DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
雨云;给项目一台云服务器
RackNerd;$21.99/年 · 1GB KVM

Python 离开 except 后读取异常变量报 NameError

Python 3 会在 except 结束时清除 as 后面的目标名字,同名初值也不会恢复。将临时别名与业务变量分开,按需保存错误文本或异常引用,并覆盖无异常路径。

在 except ValueError as exc: 内可以打印 exc,离开处理块后再读取它,却得到 NameError。如果相同代码放在普通函数里,读取被清除的局部名字还可能表现为 UnboundLocalError。提前给 exc 赋一个初值,或在处理块里写 exc = exc,都不能保住这个名字。

这是 Python 3 对异常目标变量的清理规则。下面讨论普通 except,不涉及异常组的 except*。排查时应先确认报错行确实位于处理块之后,并且前面的处理块已执行;如果异常根本没发生,变量从未绑定是另一条需要覆盖的执行路径。

被清除的是 as 后面的名字

这个示例能体现问题:

exc = "initial value"
try:
    int("not-a-number")
except ValueError as exc:
    print(str(exc))

print(exc)  # 在模块级执行时预期触发 NameError

进入处理块时,exc 被绑定到异常对象。处理块结束时,这个目标名字会被清除,原先的字符串绑定也不会恢复。即使在块内把 exc 改成另一个字符串,结束时仍会清除同一个名字。

except 没有为这个别名创建一个会自动恢复外层值的独立作用域。这里也不能通过“提前初始化同名变量”修复。应把长期使用的业务变量与临时异常别名分开命名。

清理异常引用与 traceback 有关:异常对象、回溯和栈帧可能形成引用环,使帧中的局部对象继续存活。清除目标绑定并不意味着所有指向这个异常对象的引用都被删除,也不能据此推断对象一定立即销毁。

只需要错误信息时,在块内保存文本

如果后续代码只是展示错误原因,可以把消息保存到另一个变量,同时给没有异常的路径明确初值:

def parse_integer(text):
    value = None
    error_message = None
    try:
        value = int(text)
    except ValueError as exc:
        error_message = str(exc)
    return value, error_message

value, error_message = parse_integer("not-a-number")
if error_message is not None:
    print(error_message)
else:
    print(value)

结束时清除的是 exc,error_message 仍保存字符串。这里返回二元组是示例采用的错误返回约定;接入已有程序时,应与调用方的接口保持一致,不能只改被调用函数而漏掉调用位置。

示例只处理字符串无法转换为整数的 ValueError。其他类型的异常仍向上传播,不需要为了保存文本而扩大成捕获所有异常。实际错误消息也可能包含输入内容或路径,是否展示或写入日志应按应用规则处理。

如果错误只需在捕获时处理,可以直接把日志或提示留在 except 内,不必把异常对象带出处理块。

确实需要异常对象时,使用不同的引用名

有些后续逻辑要读取异常类型或专用属性,单纯的字符串不够。可以用不同名字保存对象,并覆盖没有异常的分支:

saved_error = None
try:
    int("not-a-number")
except ValueError as exc:
    saved_error = exc

if saved_error is not None:
    print(type(saved_error).__name__)
    print(str(saved_error))

saved_error = None

这保留的是同一个异常对象的另一个引用,不是复制异常。最后清除不再使用的引用,可以避免此变量继续持有对象;若其他位置也保存了它,那些引用不会随之消失。不要无期限把完整异常对象堆在全局列表或长寿命缓存里,尤其是回溯仍关联着较大的局部数据时。

如果原来已经有 error_message 这样的业务变量,临时别名应选 exc 等不同名称。仅在处理块之后重建被删除的名字,无法恢复此前已经丢掉的业务值。

同时验证异常路径和正常路径

用独立样例分别传入能转换的字符串与不能转换的字符串,确认正常路径有返回值,失败路径保留预期消息,且两条路径都不会在后续读取未绑定的名字。检查同一函数里的其他 except ... as ...,避免复用仍需保留的结果变量。

修复范围应落在别名、需要保留的数据及相应调用位置。保留修改前的函数与调用方版本;如果错误返回约定不符合原接口,要一起回退或调整,不能用捕获后续 NameError 的方式掩盖数据已经丢失。