7.Python 错误与异常处理深度解析
Python 异常处理核心要点 Python 异常处理机制包括错误分类、捕获处理、主动抛出和自定义异常。语法错误(SyntaxError)在解析阶段出现,而异常(Exception)发生在运行时。通过 try/except 结构可捕获特定异常,使用 else 处理成功情况,finally 确保资源清理。raise 可主动抛出异常,支持异常链。自定义异常应继承 Exception 类,可携带附加信息
Python 错误与异常处理深度解析
目录
- 错误与异常概述
- 1.1 语法错误 vs 异常
- 1.2 异常的层次结构
- 基本异常处理:try/except
- 2.1 捕获特定异常
- 2.2 捕获多个异常
- 2.3 获取异常对象
- 完整的异常处理结构
- 3.1 else 子句
- 3.2 finally 子句
- 3.3 执行顺序与优先级
- 抛出异常:raise
- 4.1 基本使用
- 4.2 异常链与显式上下文
- 自定义异常
- 5.1 创建异常类
- 5.2 异常携带附加信息
- 高级技巧
- 6.1 异常日志与 traceback
- 6.2 断言 assert
- 6.3 异常抑制与上下文管理器
- 上下文管理器与异常安全
- 7.1 with 语句协议
- 7.2 contextlib 工具
- 常见陷阱与最佳实践
- 总结
1. 错误与异常概述
1.1 语法错误 vs 异常
Python 中的错误至少可以分为两类:语法错误(SyntaxError)和异常(Exception)。
- 语法错误在解析源代码阶段发生,程序根本无法执行。
while True print('Hello') # SyntaxError: invalid syntax - 异常是指在语法正确的前提下,运行时发生的错误。程序会立即终止并打印回溯信息,除非被捕获。
10 / 0 # ZeroDivisionError
1.2 异常的层次结构
所有内置异常都派生自 BaseException,常用的几支继承关系如下:
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception
├── ArithmeticError
│ ├── ZeroDivisionError
│ └── OverflowError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── OSError
├── ValueError
├── TypeError
└── ...
通常只需捕获 Exception(不包括 SystemExit 和 KeyboardInterrupt),以免意外阻止程序退出或用户中断。
2. 基本异常处理:try/except
2.1 捕获特定异常
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("除数不能为零!")
result = None
return result
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # 除数不能为零! None
2.2 捕获多个异常
可以将多个异常类型组成元组放在 except 后面,或使用多个 except 分支。
def get_item(data, index):
try:
return data[index]
except (IndexError, TypeError) as e:
print(f"访问失败:{e}")
get_item([1,2], 5) # 访问失败:list index out of range
get_item("hello", "a") # 访问失败:string indices must be integers
注意:捕获范围较窄的异常要放在前面,因为 except 是从上到下匹配的。
2.3 获取异常对象
使用 as 将异常实例绑定到变量,方便后续处理或日志记录。
try:
num = int("abc")
except ValueError as e:
print(f"转换失败: {e}") # 转换失败: invalid literal for int() with base 10: 'abc'
3. 完整的异常处理结构
3.1 else 子句
如果 try 块没有发生异常,则执行 else 块。这有助于将可能触发异常的代码与希望仅在成功时执行的逻辑分开。
def read_config(filepath):
config = {}
try:
with open(filepath) as f:
raw = f.read()
except FileNotFoundError:
print(f"文件 {filepath} 不存在")
else:
config = eval(raw) # 仅当文件读取成功后才解析
print("配置加载成功")
return config
在 else 块中出现的异常不会被前面的 except 捕获,这在逻辑上是合理的。
3.2 finally 子句
无论是否发生异常、异常是否被捕获,finally 块总是执行,常用于清理资源。
def process_file(path):
file = None
try:
file = open(path)
# 处理文件内容...
except OSError:
print("文件操作异常")
finally:
if file:
file.close()
print("文件已关闭")
即使 try 或 except 中有 return/break/continue,finally 也会在离开 try 前执行。
3.3 执行顺序与优先级
完整结构:try -> except (one or more) -> else -> finally。
- 只有
try块未触发异常时,才执行else。 finally一定会执行;如果finally中包含return,会覆盖try/except/else中的返回值。
def demo():
try:
return "try"
finally:
return "finally"
print(demo()) # 输出 "finally"
4. 抛出异常:raise
4.1 基本使用
raise 用于主动触发异常,可携带描述信息。
def set_age(age):
if not isinstance(age, int):
raise TypeError("年龄必须是整数")
if age < 0:
raise ValueError("年龄不能为负")
return age
# set_age("x") 引发 TypeError
# set_age(-5) 引发 ValueError
可以重新抛出当前正在处理的异常:
try:
1 / 0
except ZeroDivisionError:
print("记录日志后重新抛出")
raise # 将原异常继续向上传播
4.2 异常链与显式上下文
在 except 块中引发新的异常时,Python 3 会自动将原异常附加到新异常的 __context__ 属性上,形成异常链(During handling of the above exception, another exception occurred)。
try:
int("abc")
except ValueError as e:
raise RuntimeError("数据格式异常") from e # 显式使用 from 表明因果关系
使用 from None 可以抑制上下文:
try:
int("abc")
except ValueError:
raise RuntimeError("数据无效") from None # 不会有 "During handling..."
5. 自定义异常
5.1 创建异常类
自定义异常应继承 Exception(或其子类),保持命名以 Error 结尾(惯例)。
class InsufficientFundsError(Exception):
"""余额不足异常"""
pass
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(f"余额 {balance} 不足,无法取出 {amount}")
return balance - amount
5.2 异常携带附加信息
可以在自定义异常中重写 __init__ 保存更多上下文。
class APIError(Exception):
def __init__(self, message, status_code=None, response=None):
super().__init__(message)
self.status_code = status_code
self.response = response
try:
raise APIError("Not Found", 404, "无此资源")
except APIError as e:
print(f"HTTP {e.status_code}: {e}")
6. 高级技巧
6.1 异常日志与 traceback
使用 logging 模块记录异常信息,同时打印完整回溯。
import logging
import traceback
logging.basicConfig(level=logging.ERROR)
def dangerous():
1 / 0
try:
dangerous()
except Exception:
logging.error("发生未预料的异常", exc_info=True) # 打印 traceback
# 或者手动获取
tb = traceback.format_exc()
print(tb)
6.2 断言 assert
assert 用于调试时强制检查某个条件,如果为 False 则抛出 AssertionError。不建议用于正式的错误处理,因为断言可能被 -O 优化标志禁用。
def sqrt(x):
assert x >= 0, "不能对负数开平方根"
return x ** 0.5
sqrt(-1) # AssertionError: 不能对负数开平方根
6.3 异常抑制与上下文管理器
有时我们需要忽略特定的异常,可以用上下文管理器简洁地实现。
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("not_exist.file") # 文件不存在也不会崩
7. 上下文管理器与异常安全
7.1 with 语句协议
实现 __enter__ 和 __exit__ 的对象可以用 with 保证资源分配与释放。__exit__ 会接收异常信息,如果返回 True 则可抑制异常。
class Transaction:
def __enter__(self):
print("开始事务")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
print("提交事务")
else:
print(f"回滚事务,因为 {exc_type.__name__}: {exc_val}")
return False # 不抑制异常
with Transaction() as tx:
print("执行操作")
# raise ValueError("故意出错") # 会触发回滚
7.2 contextlib 工具
contextlib.contextmanager 装饰器可以将生成器函数转换为上下文管理器,更方便处理异常。
from contextlib import contextmanager
@contextmanager
def managed_resource(filename):
f = open(filename)
try:
yield f
except Exception:
print("发生异常,做清理...")
finally:
f.close()
with managed_resource("data.txt") as file:
content = file.read()
8. 常见陷阱与最佳实践
- 避免裸
except::这会捕获SystemExit和KeyboardInterrupt,导致程序无法正常退出。 至少使用except Exception:。 - 不要过度捕获异常:只处理你预期、知道如何处理的异常,其余向上层传播。
- 记录但不要隐藏:
except: pass可能将 bug 完全隐藏,难以调试。至少记录日志。 - finally 并非绝对安全:如果
finally块中又引发异常,会覆盖原先的异常。尽量保持finally简洁。 - 使用
else明确意图:把仅当成功才运行的代码放入else,降低被意外捕获的风险。 - 合理自定义异常:给特定领域添加有意义的异常类型,方便上层按类型处理。
- 慎用断言:断言可能被跳过,不应依赖它做参数校验或业务逻辑。
- 利用异常链:在重新抛出时
raise NewError(...) from original_exception,保留上下文以便调试。
9. 总结
异常是 Python 控制流中不可或缺的部分。通过合理的 try/except/else/finally 结构,我们可以让程序在面对错误时依然可控,同时保持资源的正确释放。自定义异常与异常链则让错误传递具有更强表现力。
结合上下文管理器,Python 提供了一套优雅的“申请-使用-无论成功失败都清理”的范式。理解这些工具,并在实际代码中遵循最佳实践,能显著提升程序的健壮性与可维护性。
更多推荐

所有评论(0)