为何Python的exec()执行含列表推导式的字符串代码时会报NameError?
列表推导式在exec中报NameError的原因
这个问题是三个机制共同作用的结果:
- Python3中所有列表/集合/字典推导式、生成器表达式,都会在底层创建一个独立的隐式函数作用域,和外层代码隔离开。普通for循环不会新建作用域,循环内代码和外层代码共用同一个变量表,而推导式找变量要严格遵循LEGB作用域查找规则:先查自己的本地变量,再查外层闭包绑定的变量,再查全局变量,最后查内置变量。
- 闭包的外层变量绑定是代码编译阶段就静态确定的,不会识别运行时动态插入的变量。
- 你在unittest的测试函数/测试方法里调用
exec(cell.source)时,没有显式指定命名空间,此时exec()会默认把执行过程中定义的变量(比如你这里的pattern)动态写入当前测试函数的locals()字典。但这些动态插入的变量不在编译期确定的闭包绑定列表里,推导式生成的隐式函数根本找不到它,就会抛出NameError。
你换成普通for循环能正常运行,本质是因为for循环不新建作用域,循环代码直接在exec的执行上下文里读本地变量表,能拿到动态写入的pattern。而Jupyter Notebook的单元格是在模块全局作用域执行的,模块级的locals()和globals()是同一个字典,exec动态写入的变量直接进全局命名空间,推导式查全局作用域时能找到pattern,所以不会报错。
修复方法
调用exec时显式传入一个独立的命名空间字典作为全局命名空间,让所有执行代码的变量都落在这个全局字典里,推导式就能正常查到变量了:
# 预先把测试需要的前置变量放进命名空间 exec_namespace = {"y": y} exec(cell.source, exec_namespace) # 后续校验直接从命名空间取变量即可 assert "pattern" in exec_namespace assert "z" in exec_namespace
这种写法不管学生用列表推导式还是普通for循环,都能正常执行,不会出现作用域找不到变量的问题。
内容的提问来源于stack exchange,提问作者ritzdiamond
相关产品推荐
相关产品推荐

