Python重复调用函数时except块后变量e出现UnboundLocalError问题排查
这个问题其实是Python早期版本(3.7之前)的一个特殊行为导致的:except ... as e中的异常变量e会在except块执行结束后被自动清除,哪怕你在函数开头已经初始化过同名变量。
你可能会疑惑:明明循环里每次都触发了异常,e已经被赋值了,为什么最后还会报UnboundLocalError?这是因为每次except块执行完毕后,Python会把e的引用销毁(目的是避免异常对象因为被变量引用而无法被垃圾回收)。所以当循环结束后,e已经变成了未绑定的局部变量,这时候再访问它自然就会报错。
再加上Python的变量作用域规则:一旦你在函数内部的任何地方给变量e赋值(包括except ... as e这种形式),Python会将e标记为整个函数的局部变量,覆盖你在函数开头初始化的那个e。这就导致即使开头赋值了,最后访问的还是那个被标记为局部、但已经被清除的e。
修复方案
最简单的办法是用一个单独的变量来保存最后一次的异常,避免直接使用except块里的e:
from typing import Callable import logging def repeat_calls(func: Callable, n_calls: int, raise_error: bool, error_msg: str, **kwargs): last_exception = None # 用这个变量保存最后一次异常 for ii in range(n_calls): try: func(**kwargs) return except BaseException as e: last_exception = e # 每次捕获异常后保存到外部变量 logging.warning(f'{ii+1} failure(s)! -> {error_msg}: {e}') # 这里使用last_exception而不是e logging.error(f'{n_calls} failure(s). Done trying. {error_msg}: {last_exception}') if raise_error: raise last_exception
这个方案的优势:
- 彻底避开了Python对
except块变量的自动清除行为 - 代码逻辑更清晰,明确保存的是最后一次的异常对象
另外,如果你能升级到Python 3.7或更高版本,这个问题会自动消失——因为Python 3.7修改了这个行为,except块中的异常变量不会再被自动清除,你原来的代码在3.7+环境下就能正常运行。
对应你的场景验证
从你的日志来看,两次调用都触发了参数错误的异常,说明每次循环都进入了except块,但因为每次except块结束后e被清除,循环结束后e已经不存在了,所以最后执行logging.error时就抛出了UnboundLocalError。用上面的修复方案就能完美解决这个问题。
内容的提问来源于stack exchange,提问作者ryan-entergrid

