Python实战第10期:异常处理

引言:为什么需要异常处理?

在前面的课程中,我们编写的程序都是"理想状态"下运行的。但现实中,程序经常会遇到各种意外情况:

  • 用户输入了错误的数据
  • 文件不存在
  • 网络连接失败
  • 除以零

如果没有异常处理,程序会直接崩溃。有了异常处理,我们可以优雅地处理这些错误,让程序继续运行或给出友好的提示。

异常处理是编写健壮程序的关键,掌握了异常处理,你的程序就能应对各种意外情况了。


一、try/except语句

1. 什么是异常

异常是程序运行时发生的错误。当Python遇到无法处理的情况时,会抛出一个异常。

python 复制代码
# 除以零
print(10 / 0)  # ZeroDivisionError: division by zero

# 访问不存在的变量
print(x)  # NameError: name 'x' is not defined

# 类型错误
print("10" + 10)  # TypeError: can only concatenate str (not "int") to str

2. 基本的try/except语法

python 复制代码
# 基本语法
try:
    # 可能出错的代码
    代码块
except 异常类型:
    # 发生异常时执行的代码
    处理代码

# 示例
try:
    result = 10 / 0
except ZeroDivisionError:
    print("错误:不能除以零!")

print("程序继续运行...")

输出:

erlang 复制代码
错误:不能除以零!
程序继续运行...

3. 捕获异常信息

python 复制代码
# 获取异常信息
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"发生错误:{e}")
    print(f"错误类型:{type(e)}")

输出:

kotlin 复制代码
发生错误:division by zero
错误类型:<class 'ZeroDivisionError'>

4. 捕获多种异常

python 复制代码
# 捕获多种异常
try:
    num = int(input("请输入一个数字:"))
    result = 10 / num
    print(f"结果是:{result}")
except ValueError:
    print("错误:请输入有效的数字!")
except ZeroDivisionError:
    print("错误:不能除以零!")

5. 捕获所有异常

python 复制代码
# 捕获所有异常(不推荐)
try:
    result = 10 / 0
except Exception as e:
    print(f"发生错误:{e}")

# 更安全的做法:捕获具体异常
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"除以零错误:{e}")
except ValueError as e:
    print(f"值错误:{e}")

💡 提示 :尽量避免使用 except Exception 捕获所有异常,这样会掩盖真正的错误。

6. try/except/else/finally

python 复制代码
# 完整语法
try:
    # 可能出错的代码
    num = int(input("请输入一个数字:"))
    result = 10 / num
except ValueError:
    # 发生ValueError时执行
    print("错误:请输入有效的数字!")
except ZeroDivisionError:
    # 发生ZeroDivisionError时执行
    print("错误:不能除以零!")
else:
    # 没有异常时执行
    print(f"计算成功,结果是:{result}")
finally:
    # 无论是否发生异常都会执行
    print("程序执行完毕")

💡 提示:

  • else:只在没有异常时执行
  • finally:无论是否发生异常都会执行,常用于清理资源

7. 实际应用示例

python 复制代码
def read_file(filename):
    """安全地读取文件"""
    try:
        with open(filename, "r", encoding="utf-8") as f:
            content = f.read()
        return content
    except FileNotFoundError:
        print(f"错误:文件 '{filename}' 不存在")
        return None
    except PermissionError:
        print(f"错误:没有权限读取文件 '{filename}'")
        return None
    except Exception as e:
        print(f"发生未知错误:{e}")
        return None

# 测试
content = read_file("test.txt")
if content:
    print(f"文件内容:{content[:50]}...")

二、常见异常类型

1. 常见异常一览表

异常类型 说明 示例
SyntaxError 语法错误 print("hello"
NameError 变量未定义 print(x)
TypeError 类型错误 "10" + 10
ValueError 值错误 int("abc")
IndexError 索引错误 lst[10]
KeyError 键错误 dict["key"]
AttributeError 属性错误 obj.nonexistent
ZeroDivisionError 除以零 10 / 0
FileNotFoundError 文件不存在 open("no.txt")
ImportError 导入错误 import xxx
IOError 输入输出错误 文件读写错误
KeyboardInterrupt 用户中断 Ctrl+C

2. SyntaxError(语法错误)

python 复制代码
# 语法错误在代码运行前就会被检测到
# print("hello"  # 缺少右括号
# if True  # 缺少冒号
#     print("yes")

3. NameError(名称错误)

python 复制代码
# 使用未定义的变量
# print(x)  # NameError: name 'x' is not defined

# 解决方法:先定义变量
x = 10
print(x)  # 输出:10

4. TypeError(类型错误)

python 复制代码
# 类型不匹配
# print("年龄:" + 20)  # TypeError

# 解决方法:类型转换
print("年龄:" + str(20))  # 输出:年龄:20

# 或者使用格式化字符串
age = 20
print(f"年龄:{age}")  # 输出:年龄:20

5. ValueError(值错误)

python 复制代码
# 转换失败
# num = int("abc")  # ValueError

# 解决方法:使用try/except
try:
    num = int("abc")
except ValueError:
    print("无法转换为整数")
    num = 0  # 设置默认值

6. IndexError(索引错误)

python 复制代码
# 索引超出范围
lst = [1, 2, 3]
# print(lst[10])  # IndexError

# 解决方法:检查索引
index = 10
if 0 <= index < len(lst):
    print(lst[index])
else:
    print(f"索引 {index} 超出范围")

7. KeyError(键错误)

python 复制代码
# 访问不存在的键
d = {"name": "小明", "age": 20}
# print(d["gender"])  # KeyError

# 解决方法1:使用get()
print(d.get("gender", "未知"))  # 输出:未知

# 解决方法2:先检查
if "gender" in d:
    print(d["gender"])
else:
    print("键不存在")

8. AttributeError(属性错误)

python 复制代码
# 访问不存在的属性
class Person:
    def __init__(self, name):
        self.name = name

p = Person("小明")
# print(p.age)  # AttributeError

# 解决方法:使用hasattr()检查
if hasattr(p, "age"):
    print(p.age)
else:
    print("属性不存在")

9. ZeroDivisionError(除以零错误)

python 复制代码
# 除以零
# result = 10 / 0  # ZeroDivisionError

# 解决方法:检查除数
divisor = 0
if divisor != 0:
    result = 10 / divisor
else:
    print("除数不能为零")
    result = None

10. FileNotFoundError(文件不存在)

python 复制代码
# 文件不存在
# with open("no_such_file.txt", "r") as f:
#     content = f.read()  # FileNotFoundError

# 解决方法:使用try/except
try:
    with open("no_such_file.txt", "r", encoding="utf-8") as f:
        content = f.read()
except FileNotFoundError:
    print("文件不存在")
    content = ""

三、自定义异常

1. 为什么需要自定义异常

有时候,内置的异常类型不能满足需求,我们需要定义自己的异常类型。

python 复制代码
# 示例:年龄验证
class AgeError(Exception):
    """年龄错误异常"""
    pass

def set_age(age):
    if age < 0:
        raise AgeError("年龄不能为负数")
    if age > 150:
        raise AgeError("年龄不能超过150岁")
    return age

# 测试
try:
    age = set_age(-5)
except AgeError as e:
    print(f"错误:{e}")

2. 创建自定义异常类

python 复制代码
# 自定义异常类
class MyError(Exception):
    """自定义异常基类"""
    pass

class ValidationError(MyError):
    """验证错误异常"""
    def __init__(self, field, message):
        self.field = field
        self.message = message
        super().__init__(f"字段 '{field}' 验证失败:{message}")

class NotFoundError(MyError):
    """未找到异常"""
    def __init__(self, resource, id):
        self.resource = resource
        self.id = id
        super().__init__(f"{resource} (ID: {id}) 未找到")

# 使用自定义异常
def validate_user(data):
    if "name" not in data:
        raise ValidationError("name", "不能为空")
    if "age" not in data:
        raise ValidationError("age", "不能为空")
    if data["age"] < 0:
        raise ValidationError("age", "不能为负数")
    return True

# 测试
try:
    validate_user({"name": "小明"})
except ValidationError as e:
    print(f"验证错误:{e}")
    print(f"字段:{e.field}")
    print(f"消息:{e.message}")

3. 异常层次结构

python 复制代码
# Python异常层次结构
# BaseException
# ├── SystemExit
# ├── KeyboardInterrupt
# ├── GeneratorExit
# └── Exception
#     ├── ArithmeticError
#     │   ├── ZeroDivisionError
#     │   ├── OverflowError
#     │   └── FloatingPointError
#     ├── AttributeError
#     ├── ImportError
#     ├── IndexError
#     ├── KeyError
#     ├── NameError
#     ├── TypeError
#     ├── ValueError
#     └── ...

# 自定义异常应该继承Exception类
class CustomError(Exception):
    """自定义异常"""
    pass

4. 异常链

python 复制代码
# 异常链:在捕获异常时抛出新的异常
class DatabaseError(Exception):
    """数据库错误"""
    pass

def query_database(sql):
    try:
        # 模拟数据库查询
        result = 10 / 0  # 模拟错误
    except ZeroDivisionError as e:
        # 抛出新的异常,保留原始异常信息
        raise DatabaseError(f"数据库查询失败:{sql}") from e

# 测试
try:
    query_database("SELECT * FROM users")
except DatabaseError as e:
    print(f"数据库错误:{e}")
    print(f"原始异常:{e.__cause__}")

5. 异常组(Python 3.11+)

python 复制代码
# Python 3.11+ 支持异常组
# 用于同时处理多个异常

# 示例:并行任务中的异常处理
async def process_tasks():
    tasks = [task1(), task2(), task3()]
    
    try:
        await asyncio.gather(*tasks)
    except ExceptionGroup as eg:
        for e in eg.exceptions:
            print(f"任务失败:{e}")

四、异常处理最佳实践

1. 只捕获必要的异常

python 复制代码
# ❌ 不推荐:捕获所有异常
try:
    result = 10 / num
except Exception:
    pass  # 掩盖了真正的错误

# ✅ 推荐:捕获具体异常
try:
    result = 10 / num
except ZeroDivisionError:
    print("除数不能为零")
except TypeError:
    print("类型错误")

2. 不要忽略异常

python 复制代码
# ❌ 不推荐:忽略异常
try:
    do_something()
except Exception:
    pass  # 错误被忽略了

# ✅ 推荐:至少记录异常
import logging

try:
    do_something()
except Exception as e:
    logging.error(f"发生错误:{e}")
    # 可以选择重新抛出或处理

3. 使用finally清理资源

python 复制代码
# ✅ 推荐:使用finally确保资源被释放
file = None
try:
    file = open("data.txt", "r", encoding="utf-8")
    content = file.read()
    # 处理内容
finally:
    if file:
        file.close()  # 确保文件被关闭

# 更好的方式:使用with语句
with open("data.txt", "r", encoding="utf-8") as file:
    content = file.read()

4. 使用上下文管理器

python 复制代码
from contextlib import contextmanager

@contextmanager
def open_file(filename, mode="r"):
    """文件上下文管理器"""
    file = None
    try:
        file = open(filename, mode, encoding="utf-8")
        yield file
    finally:
        if file:
            file.close()

# 使用
with open_file("data.txt", "r") as f:
    content = f.read()

5. 异常处理策略

python 复制代码
# 策略1:快速失败
def divide(a, b):
    if b == 0:
        raise ValueError("除数不能为零")
    return a / b

# 策略2:返回默认值
def safe_divide(a, b, default=0):
    try:
        return a / b
    except ZeroDivisionError:
        return default

# 策略3:记录并继续
import logging

def process_data(data_list):
    results = []
    for data in data_list:
        try:
            result = process(data)
            results.append(result)
        except Exception as e:
            logging.error(f"处理数据失败:{data},错误:{e}")
            continue
    return results

五、实战练习

练习1:安全的用户输入

python 复制代码
def get_valid_age():
    """获取有效的年龄输入"""
    while True:
        try:
            age = int(input("请输入年龄(0-150):"))
            if 0 <= age <= 150:
                return age
            else:
                print("年龄必须在0-150之间")
        except ValueError:
            print("请输入有效的整数")

# 测试
age = get_valid_age()
print(f"您输入的年龄是:{age}")

练习2:文件处理器

python 复制代码
import json
from pathlib import Path

class FileProcessor:
    """文件处理器"""
    
    def __init__(self, filename):
        self.filename = Path(filename)
    
    def read_json(self):
        """读取JSON文件"""
        try:
            with self.filename.open("r", encoding="utf-8") as f:
                return json.load(f)
        except FileNotFoundError:
            print(f"文件不存在:{self.filename}")
            return {}
        except json.JSONDecodeError as e:
            print(f"JSON格式错误:{e}")
            return {}
    
    def write_json(self, data):
        """写入JSON文件"""
        try:
            with self.filename.open("w", encoding="utf-8") as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            return True
        except Exception as e:
            print(f"写入失败:{e}")
            return False

# 测试
processor = FileProcessor("config.json")
data = processor.read_json()
data["version"] = "1.0.0"
processor.write_json(data)

练习3:自定义异常体系

python 复制代码
class AppError(Exception):
    """应用错误基类"""
    def __init__(self, message, code=None):
        super().__init__(message)
        self.code = code

class ValidationError(AppError):
    """验证错误"""
    def __init__(self, field, message):
        super().__init__(f"字段 '{field}' 验证失败:{message}", code="VALIDATION_ERROR")
        self.field = field

class NotFoundError(AppError):
    """未找到错误"""
    def __init__(self, resource, id):
        super().__init__(f"{resource} (ID: {id}) 未找到", code="NOT_FOUND")
        self.resource = resource
        self.id = id

class AuthenticationError(AppError):
    """认证错误"""
    def __init__(self, message="认证失败"):
        super().__init__(message, code="AUTH_ERROR")

# 使用
def create_user(data):
    if "name" not in data:
        raise ValidationError("name", "不能为空")
    if "email" not in data:
        raise ValidationError("email", "不能为空")
    return {"id": 1, **data}

# 测试
try:
    user = create_user({"name": "小明"})
except ValidationError as e:
    print(f"验证错误:{e}")
    print(f"错误代码:{e.code}")
    print(f"字段:{e.field}")
except AppError as e:
    print(f"应用错误:{e}")
    print(f"错误代码:{e.code}")

练习4:重试机制

python 复制代码
import time
import random

def retry(max_attempts=3, delay=1, exceptions=(Exception,)):
    """重试装饰器"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except exceptions as e:
                    if attempt == max_attempts:
                        print(f"第 {attempt} 次尝试失败,放弃重试")
                        raise
                    print(f"第 {attempt} 次尝试失败:{e},{delay}秒后重试...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=1, exceptions=(ConnectionError,))
def unstable_api_call():
    """模拟不稳定的API调用"""
    if random.random() < 0.7:  # 70%概率失败
        raise ConnectionError("连接失败")
    return {"status": "success"}

# 测试
try:
    result = unstable_api_call()
    print(f"调用成功:{result}")
except ConnectionError as e:
    print(f"最终失败:{e}")

六、常见问题与排错

问题1:异常被忽略

原因 :使用空的except块 解决方法:至少记录异常信息

python 复制代码
# ❌ 错误
try:
    do_something()
except:
    pass  # 异常被忽略了

# ✅ 正确
try:
    do_something()
except Exception as e:
    print(f"发生错误:{e}")
    # 或者记录日志
    # logging.error(f"发生错误:{e}")

问题2:异常处理顺序

原因 :父类异常在前,子类异常在后 解决方法:子类异常在前,父类异常在后

python 复制代码
# ❌ 错误
try:
    result = 10 / 0
except Exception:  # 父类在前
    print("发生异常")
except ZeroDivisionError:  # 子类在后,永远不会执行
    print("除以零")

# ✅ 正确
try:
    result = 10 / 0
except ZeroDivisionError:  # 子类在前
    print("除以零")
except Exception:  # 父类在后
    print("其他异常")

问题3:finally中的return

原因 :finally中的return会覆盖try/except中的return 解决方法:避免在finally中使用return

python 复制代码
# ❌ 不推荐
def test():
    try:
        return 1
    finally:
        return 2  # 会覆盖try中的return

print(test())  # 输出:2

# ✅ 推荐:避免在finally中return
def test():
    try:
        return 1
    finally:
        pass  # 只做清理工作

print(test())  # 输出:1

七、本篇小结

今天,我们完成了以下学习内容:

  1. try/except语句:

    • 基本的异常捕获语法
    • 捕获多种异常
    • try/except/else/finally完整语法
  2. 常见异常类型:

    • SyntaxError、NameError、TypeError
    • ValueError、IndexError、KeyError
    • AttributeError、ZeroDivisionError
    • FileNotFoundError等
  3. 自定义异常:

    • 创建自定义异常类
    • 异常层次结构
    • 异常链
    • 异常组(Python 3.11+)
  4. 异常处理最佳实践:

    • 只捕获必要的异常
    • 不要忽略异常
    • 使用finally清理资源
    • 异常处理策略

请记住这三个结论:

  1. 异常处理让程序更健壮:使用try/except捕获和处理异常
  2. 捕获具体异常 :避免使用 except Exception 捕获所有异常
  3. 自定义异常提高可读性:为特定场景创建自定义异常类

下期预告

在第11期中,我们将学习Python的面向对象编程基础,包括:

  • 类和对象的概念
  • 属性和方法的定义
  • __init__ 构造方法
  • 实例属性和类属性

敬请期待!


✍坚持原创,求关注,点赞,收藏

相关推荐
余槐i2 小时前
加了Cookie yt-dlp仍报HTTP 403:3个容易漏的请求头差异
python·http·ffmpeg·开发工具·yt-dlp
专业程序开发源2 小时前
springboot校企合作双选会一站式预约与面试系统16010-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·面试·php·课程设计
菜鸟不秃头2 小时前
手搓 ViT -- 并完成图像分类任务
python
ZhuoAIStage2 小时前
舞台工艺检测表单自动生成:用 Python 与智能体 Skill 打通规范限值到可填表的数据流
python·第三方检测·舞台工艺·表单自动生成·规范性引用
郑州光合科技余经理2 小时前
同城商城源码:商品库和订单库怎么随包交付
android·java·开发语言·后端·uni-app·php·ai编程
MoRanzhi12033 小时前
第3篇 · 使用 PyQt5 实现 2048 桌面版游戏:MVC 架构与撤销和重做
python·mvc·pyqt5·桌面应用开发·gui开发·2048游戏·撤销与重做
蚰蜒螟3 小时前
从 Java SocketChannel 到 TCP 挥手:一次 close() 的完整调用链与引用计数解析
java·python·tcp/ip
Smoothcloud润云3 小时前
GPU算力平台跑知识库问答,模型回答总跑偏?先排查检索链路
人工智能·python·深度学习·gpu算力·gpu
mklpo1473 小时前
python3:如何按照实际执行需求导入模块,而不是启动时统一导入?(Python3.15)。2026-10-20
开发语言·人工智能·python