异常是程序运行过程中发生的错误或意外情况。Python 通过一套完整的异常处理机制,让开发者能够优雅地捕获、处理这些错误,避免程序直接崩溃。
1. 异常处理的基本语法:try / except / else / finally
python
try:
# 可能抛出异常的代码
x = int(input("请输入数字: "))
result = 10 / x
except ValueError:
print("输入不是有效数字")
except ZeroDivisionError:
print("除数不能为零")
else:
# 仅当 try 块未发生任何异常时执行
print(f"计算成功,结果是 {result}")
finally:
# 无论是否发生异常,都会执行(清理资源等)
print("执行结束")
执行流程:
- 先执行
try块; - 如果发生异常,匹配对应的
except分支; - 若无异常,执行
else块; - 最后无论有无异常都执行
finally块。
2. 异常类的继承层次结构
Python 所有异常都继承自 BaseException 基类。继承树(简化版):
BaseException
├── SystemExit # sys.exit() 触发
├── KeyboardInterrupt # Ctrl+C 触发
├── GeneratorExit # 生成器 close() 触发
└── Exception # 所有常规异常的基类
├── StopIteration # 迭代器结束
├── ArithmeticError
│ ├── ZeroDivisionError
│ ├── OverflowError
│ └── FloatingPointError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── TypeError
├── ValueError
├── AttributeError
├── ImportError
├── OSError
│ └── FileNotFoundError
├── RuntimeError
└── ... (数十种其他异常)
重要原则:
- 通常自定义异常应该继承自
Exception,而不是BaseException。 - 捕获
Exception可以捕获除SystemExit、KeyboardInterrupt、GeneratorExit之外的所有异常。 - 捕获
BaseException会连系统退出信号一起捕获,极少这么做。
python
try:
# 业务代码
...
except Exception as e:
# 捕获几乎所有的常规错误
print(f"出错: {e}")
3. 手动抛出异常:raise
raise 语句允许主动触发一个异常,可以传递异常类或异常实例。
python
def set_age(age):
if not isinstance(age, int):
raise TypeError("年龄必须是整数")
if age < 0 or age > 150:
raise ValueError("年龄超出合理范围")
print(f"年龄设置为 {age}")
# 调用示例
try:
set_age(-5)
except ValueError as e:
print(e) # 年龄超出合理范围
常见用法:
raise ExceptionClass("描述信息")raise instance:例如raise ValueError("错误内容")- 不带参数重新抛出当前异常(在
except块中)
python
try:
risky_operation()
except ValueError as e:
print("记录日志...")
raise # 重新抛出,保留原始调用栈
4. 自定义异常
继承 Exception 或其子类,可以添加额外属性和方法。
python
class InsufficientFundsError(Exception):
"""账户余额不足时抛出的异常"""
def __init__(self, balance, required):
self.balance = balance
self.required = required
super().__init__(f"余额不足: 需要 {required}, 现有 {balance}")
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
return self.balance
# 使用
try:
account = BankAccount(100)
account.withdraw(200)
except InsufficientFundsError as e:
print(e) # 余额不足: 需要 200, 现有 100
print(f"余额: {e.balance}, 需要: {e.required}")
最佳实践:
- 自定义异常命名以
Error结尾。 - 提供有意义的错误信息和额外属性。
- 通常不重写
__str__,利用基类的机制即可。
5. 异常传递(传播机制)
当函数内部发生异常且没有被捕获时,该异常会向调用栈的上层传递,直到被某个 try/except 捕获;如果传递到主程序仍未捕获,程序终止并打印回溯信息。
python
def func_a():
return 1 / 0 # 抛出 ZeroDivisionError
def func_b():
return func_a() # 未捕获,继续向上传递
def func_c():
try:
func_b()
except ZeroDivisionError as e:
print(f"在 func_c 中捕获: {e}")
func_c() # 输出: 在 func_c 中捕获: division by zero
调用栈示意图:
main() -> func_c() -> func_b() -> func_a() -> ZeroDivisionError
↑ 捕获
重新抛出(传递) :
有时需要部分处理异常(如记录日志),然后让上层继续处理。
python
def process_file(filename):
try:
f = open(filename)
# 处理文件
except FileNotFoundError as e:
print(f"日志: 文件 {filename} 不存在")
raise # 重新抛出,让上层决定是否恢复或退出
6. 异常链与 from 关键字
在处理一个异常时抛出另一个异常,可以使用 from 保留原始异常信息。
python
try:
value = int("abc")
except ValueError as original:
raise RuntimeError("转换失败") from original
# 输出会显示两个异常,明确因果关系
7. assert 断言与异常
assert condition, message 在条件为假时抛出 AssertionError。通常用于调试和内部检查,不应作为业务逻辑的异常控制。
python
def divide(a, b):
assert b != 0, "除数不能为零" # 开发阶段快速发现错误
return a / b
8. 常见陷阱与注意事项
-
捕获过于宽泛 :
except:会捕获BaseException,包括KeyboardInterrupt和SystemExit,导致无法正常退出程序。推荐except Exception as e:。 -
finally中的return会覆盖之前的异常 :pythondef test(): try: raise ValueError finally: return 42 # 异常被吞没,函数返回 42 -
else块与return:else中的异常不会被前面的except捕获,因为else不属于try保护区域。 -
性能 :异常处理比正常流程稍慢,不应将其用于常规流程控制(如用
try/except替代if检查)。
9. 完整示例:多层异常处理
python
class ValidationError(Exception):
pass
def validate_email(email):
if "@" not in email:
raise ValidationError(f"无效邮箱: {email}")
def register_user(email, age):
try:
validate_email(email)
if age < 18:
raise ValueError("未成年不能注册")
except ValidationError as e:
print(f"邮箱错误: {e}")
raise # 向上传递
except ValueError as e:
print(f"年龄错误: {e}")
raise
try:
register_user("user#example.com", 20)
except ValidationError:
print("注册失败: 请检查邮箱格式")
except ValueError:
print("注册失败: 年龄不符合要求")
总结
| 概念 | 关键点 |
|---|---|
| 异常机制 | try/except/else/finally 捕获与处理 |
| 继承层次 | BaseException <- Exception <- 具体异常;自定义应继承 Exception |
| 手动抛出 | raise Exception("msg") |
| 自定义异常 | 继承 Exception,添加有用属性 |
| 异常传递 | 未捕获的异常沿调用栈向上传播,直至被捕获或终止程序 |
| 异常链 | from 关键字保留上下文 |
| 最佳实践 | 捕获具体异常、避免吞没异常、finally 用于资源释放、不滥用异常做流程控制 |
掌握这些内容,你就能在 Python 中设计出健壮、可维护的错误处理逻辑。