Python中exec执行带过滤条件的列表推导时的奇怪作用域问题
Python exec执行时列表推导过滤条件的作用域异常问题
在Python 3.8、3.9、3.10版本中,使用exec执行代码块时,中途定义的变量在列表推导的过滤条件中可能出现作用域异常,导致NameError错误。
现象对比
始终正常运行的示例
无论直接使用exec还是通过函数包裹exec执行,以下代码都能正常运行:
a = [1, 2] b = [i for i in a]
常运行失败的示例
当列表推导包含过滤条件if i in a时,通过自定义函数包裹exec执行会抛出NameError: name 'a' is not defined:
a = [1, 2] b = [i for i in a if i in a]
exec执行的对比测试
from pathlib import Path # 创建execwrap模块,用函数包裹exec Path("execwrap.py").write_text(""" def execwrap(*args, **kwargs): exec(*args, **kwargs) """) import execwrap # 直接调用exec:成功执行 exec("a=[1,2];b=[i for i in a if i in a]") # 通过execwrap执行:抛出NameError execwrap.execwrap("a=[1,2];b=[i for i in a if i in a]") # 错误栈: # NameError: name 'a' is not defined # 传入空的globals和locals参数:同样失败 execwrap.execwrap("a=[1,2];b=[i for i in a if i in a]", {}, {}) # 显式传入当前全局作用域:成功执行 execwrap.execwrap("a=[1,2];b=[i for i in a if i in a]", globals(), {}) # 不带过滤条件的列表推导:成功执行 execwrap.execwrap("a=[1,2];b=[i for i in a]")
原因分析
- Python 3的列表推导拥有独立的局部作用域,但变量查找规则在迭代部分和过滤条件部分存在差异:
- 迭代部分(
for i in a)会优先从exec创建的作用域中查找变量; - 过滤条件(
if i in a)则会优先查找列表推导自身的作用域,再向上查找包裹exec的函数作用域,不会直接访问exec内部的作用域。
- 迭代部分(
- 当在
execwrap函数中调用exec且未指定globals参数时,exec默认使用函数的局部作用域作为locals,而a是在exec内部定义的,不属于函数的作用域,因此过滤条件无法找到a。 - 直接调用
exec时,默认使用当前的全局和局部作用域,过滤条件可以正常找到a;显式传入globals()时,a被定义在全局作用域,过滤条件也能找到。
解决方案
- 显式指定全局作用域:执行
exec时传入当前环境的globals()作为globals参数,让变量被定义在全局作用域:
execwrap.execwrap("a=[1,2];b=[i for i in a if i in a]", globals(), {})
- 共用作用域字典:将exec的globals和locals设置为同一个字典,确保变量在同一作用域内:
scope = {} execwrap.execwrap("a=[1,2];b=[i for i in a if i in a]", scope, scope) # 可通过scope['a']、scope['b']访问结果
- 提前定义变量:将需要在过滤条件中引用的变量提前定义在
exec外部的作用域中,避免在exec内部临时定义。
内容的提问来源于stack exchange,提问作者Simon Streicher
相关产品推荐
相关产品推荐

