(五)异常沿调用链传播
1. 从失败位置回到能处理它的调用层
第四章已经用 try/except 观察属性更新失败。文件处理会同时遇到“文件找不到”“编码错误”“一行不是数字”等不同问题。先用三层调用跟踪一行坏数据:
# parse_minutes 的转换失败后,不会继续返回到 total 中完成加法。
def parse_minutes(text):
print("进入转换")
return int(text)
def total(lines):
result = 0
for line in lines:
result += parse_minutes(line)
return result
try:
print(total(["20", "很多", "45"]))
except ValueError:
print("有一行不是整数")输出两次“进入转换”,再输出“有一行不是整数”。第一行已转换为 20;第二行失败时,parse_minutes 和 total 都没有正常返回,第三行还未读取,外层的异常分支接管执行。
没有匹配处理处时,程序会显示 traceback:列出异常经过的文件、行号与调用关系,最后一行给出错误类型和说明。先找实际失败的语句,再沿调用关系检查交进来的数据,下一章会结合断点继续观察。
2. except、else 与 finally
# except 接收指定错误;else 在 try 正常结束后执行;finally 负责最后的收尾。
for text in ["35", "很多"]:
try:
minutes = int(text)
except ValueError:
print("转换失败", text)
else:
print("转换成功", minutes)
finally:
print("本轮结束")输出“转换成功 35”“本轮结束”“转换失败 很多”“本轮结束”。else 中发生的异常不会再由前面的 except 接收,所以可以把成功后的进一步处理放到那里,让“捕获哪一段操作的失败”保持明确。
finally 还会在 return 离开之前执行:
# 先执行 finally,再把已准备的结果交回调用者。
def calculate():
try:
return 55
finally:
print("完成收尾")
print(calculate())得到“完成收尾”和 55。若在 finally 中再返回另一个值,会覆盖原本的返回或正在传播的异常;收尾通常只完成清理动作。
| 常见错误 | 对应的实际问题 |
|---|---|
FileNotFoundError |
路径指向的文件不存在 |
PermissionError |
当前操作没有对应的文件访问权限 |
UnicodeDecodeError |
文件字节不能按选定编码解码 |
ValueError |
转换或业务检查发现值不合要求 |
OSError |
文件与系统操作错误的共同类别,前两项也是其子类 |
按类型从具体到一般安排多个 except。except (ValueError, TypeError) 可以处理两类错误;except Exception 能接收许多普通错误,但会把原本不同的失败归到同一处,是否还能给出清楚反馈要看处理代码做了什么。异常控制流程