Python 异常处理词典笔记
提示
来源:Python 3.14 官方文档教程 - 错误和异常章节 只做词典类用途,业务使用细节见本目录中的其他笔记
8.1. 语法错误(SyntaxError)
语法错误又称解析错误,是学 Python 最常遇到的错误。
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^^^^^
SyntaxError: invalid syntax
特点:
- 解析器会重复出错的行并显示指向检测到错误的位置的小箭头
- 错误位置不一定是需要被修复的位置(上例中缺少冒号导致
print()被标记) - 会打印文件名和行号以便定位
8.2. 异常(Exception)
语法正确不代表运行时不会出错。执行时检测到的错误叫异常。
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
10 * (1/0)
~^~
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
4 + spam*3
^^^^
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
'2' + 2
~~~~^~~
TypeError: can only concatenate str (not "int") to str
错误信息解读:
- 最后一行说明异常类型和详细信息
- 开头是堆栈回溯,展示发生异常的语境
- 常见内置异常:
ZeroDivisionError、NameError、TypeError
异常继承关系:
BaseException
├── SystemExit # sys.exit() 引发
├── KeyboardInterrupt # 用户中断(Ctrl+C)
└── Exception # 所有非致命异常的基类
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── TypeError
├── ValueError
└── ...
8.3. 异常的处理(try/except)
基本语法
try:
# 可能引发异常的代码
x = int(input("请输入一个数字: "))
except ValueError:
# 处理特定异常
print("这不是一个有效的数字!")
执行流程:
- 首先执行 try 子句(
try和except之间的语句) - 如果没有触发异常,跳过 except 子句,
try语句执行完毕 - 如果发生异常,跳过 try 子句剩余部分
- 如果异常类型与
except匹配,执行 except 子句 - 如果异常类型不匹配,传递给外层
try语句;如果没有处理器,程序终止
捕获多个异常
# 方式1:多个 except 子句
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print(f"OS 错误: {err}")
except ValueError:
print("无法转换为整数")
except Exception as err:
print(f"意外错误: {err=}, {type(err)=}")
raise # 重新抛出
# 方式2:元组指定多个异常
except (RuntimeError, TypeError, NameError):
pass
异常类的匹配规则
except子句中的类匹配该类本身或其派生类的实例- 顺序很重要!派生类在前会拦截基类异常
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
# 输出: B, C, D
# 如果颠倒顺序(B 放最前),则全部输出 B
获取异常详细信息
try:
raise Exception('参数1', '参数2')
except Exception as inst:
print(type(inst)) # <class 'Exception'>
print(inst.args) # ('参数1', '参数2')
print(inst) # ('参数1', '参数2')
x, y = inst.args # 解包
print(f'x = {x}') # x = 参数1
print(f'y = {y}') # y = 参数2
else 子句
try 语句可以有可选的 else 子句,必须放在所有 except 之后。
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('无法打开', arg)
else:
print(arg, '有', len(f.readlines()), '行')
f.close()
优点: 避免意外捕获非 try 子句保护的代码触发的异常。
异常处理的传播
异常处理程序不仅处理 try 子句中的异常,还处理 try 子句中调用的函数里的异常:
def this_fails():
x = 1/0
try:
this_fails() # 函数内部触发异常
except ZeroDivisionError as err:
print('处理运行时错误:', err)
8.4. 触发异常(raise)
基本用法
>>> raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
raise NameError('HiThere')
NameError: HiThere
- 参数必须是异常实例或异常类
- 如果是异常类,会隐式调用无参构造函数实例化
raise ValueError # 简写,等价于 raise ValueError()
raise ValueError() # 完整写法
raise ValueError('无效值') # 带参数的实例
重新抛出异常
try:
raise NameError('HiThere')
except NameError:
print('一个异常飞过了!')
raise # 重新触发当前异常
8.5. 异常链(Exception Chaining)
隐式异常链
如果在 except 块中发生了新的异常,新异常会被附加到原异常上:
try:
open("database.sqlite")
except OSError:
raise RuntimeError("unable to handle error")
# 输出:
# FileNotFoundError: [Errno 2] No such file or directory: 'database.sqlite'
# During handling of the above exception, another exception occurred:
# RuntimeError: unable to handle error
显式异常链(raise ... from ...)
def func():
raise ConnectionError
try:
func()
except ConnectionError as exc:
raise RuntimeError('Failed to open database') from exc
# 输出:
# The above exception was the direct cause of the following exception:
# RuntimeError: Failed to open database
禁用异常链
try:
open('database.sqlite')
except OSError:
raise RuntimeError from None # 不显示原始异常
8.6. 用户自定义异常
- 自定义异常应从
Exception类派生(直接或间接) - 异常类可以包含任意属性
- 命名通常以 "Error" 结尾
class ValidationError(Exception):
"""数据验证失败时抛出"""
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"字段 '{field}': {message}")
# 使用
try:
if not email:
raise ValidationError('email', '不能为空')
except ValidationError as e:
print(f"验证失败: {e}")
print(f"问题字段: {e.field}")
8.7. 定义清理操作(finally)
finally 子句定义在任何情况下都必须执行的清理操作。
try:
raise KeyboardInterrupt
finally:
print('Goodbye, world!')
# 输出:
# Goodbye, world!
# KeyboardInterrupt 被重新触发
执行规则:
- 无论是否发生异常,
finally都会执行 - 如果发生异常但没有匹配的
except,finally执行后异常重新触发 finally中的return/break/continue会抑制异常的重新触发(⚠️ 不推荐)- 如果
try中有return,finally会在返回前执行
def divide(x, y):
try:
result = x / y
except ZeroDivisionError:
print("除零错误!")
else:
print(f"结果是 {result}")
finally:
print("执行 finally 子句")
divide(2, 1) # 结果: 2.0, finally
divide(2, 0) # 除零错误!, finally
divide("2", "1") # finally, 然后 TypeError
典型用途: 释放外部资源(文件、网络连接等)
8.8. 预定义的清理操作(with 语句)
有些对象定义了标准的清理操作,无论是否成功都会执行。
问题代码:
for line in open("myfile.txt"):
print(line, end="")
# 文件在不确定的时间内保持打开
正确做法:
with open("myfile.txt") as f:
for line in f:
print(line, end="")
# 语句执行完毕后,即使发生异常,文件 f 也会被关闭
with 语句是上下文管理器的语法糖,确保资源及时关闭。
8.9. 引发和处理多个不相关的异常(ExceptionGroup)
Python 3.11+ 引入 ExceptionGroup,可以打包多个异常一起引发。
基本用法
def f():
excs = [OSError('error 1'), SystemError('error 2')]
raise ExceptionGroup('there were problems', excs)
f()
# 输出:
# ExceptionGroup: there were problems (2 sub-exceptions)
# +---------------- 1 ----------------
# | OSError: error 1
# +---------------- 2 ----------------
# | SystemError: error 2
选择性捕获(except*)
except* 只处理组中特定类型的异常:
def f():
raise ExceptionGroup(
"group1",
[
OSError(1),
SystemError(2),
ExceptionGroup(
"group2",
[OSError(3), RecursionError(4)]
)
]
)
try:
f()
except* OSError as e:
print("有 OSError")
except* SystemError as e:
print("有 SystemError")
# RecursionError 未被捕获,继续传播
收集多个异常的典型模式
excs = []
for test in tests:
try:
test.run()
except Exception as e:
excs.append(e)
if excs:
raise ExceptionGroup("Test Failures", excs)
8.10. 用注释细化异常情况(add_note)
异常有 add_note(note) 方法,可以加字符串注释,在回溯中显示。
基本用法
try:
raise TypeError('bad type')
except Exception as e:
e.add_note('添加一些信息')
e.add_note('添加更多信息')
raise
# 输出:
# TypeError: bad type
# Add some information
# Add some more information
在异常组中使用
def f():
raise OSError('operation failed')
excs = []
for i in range(3):
try:
f()
except Exception as e:
e.add_note(f'Happened in Iteration {i+1}')
excs.append(e)
raise ExceptionGroup('We have some problems', excs)
# 每个异常都有注释说明发生的迭代次数
异常处理最佳实践
- 具体捕获:尽量捕获具体的异常类型,不要滥用
except Exception - 不要吞没异常:捕获后至少记录,必要时重新抛出
- 使用上下文管理器:
with语句自动管理资源 - 合理使用 else:将正常逻辑与异常处理分离
- 用好 finally:确保清理操作被执行
- 异常链:转换异常时用
raise ... from ...保留上下文 - 自定义异常:为业务逻辑定义清晰的异常层次结构
速查表
# 基本结构
try:
# 尝试执行
pass
except SpecificError as e:
# 处理特定异常
pass
except (Error1, Error2) as e:
# 处理多个异常
pass
except Exception as e:
# 兜底处理
raise
else:
# 无异常时执行
pass
finally:
# 总是执行
pass
# 抛出异常
raise ValueError("message")
raise # 重新抛出当前异常
raise NewError() from old_exc # 异常链
# 上下文管理器
with open("file") as f:
pass # 自动关闭
# 异常组(3.11+)
raise ExceptionGroup("msg", [exc1, exc2])
except* SpecificError as e:
pass # 只捕获特定类型
# 添加注释
try:
raise Error()
except Error as e:
e.add_note("额外信息")
raise