Python 现代开发学习笔记

(五)异常沿调用链传播

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 能接收许多普通错误,但会把原本不同的失败归到同一处,是否还能给出清楚反馈要看处理代码做了什么。异常控制流程

笔记正文与全部题解 · Ctrl / ⌘ K