Python函数内调用exec赋值后访问变量触发NameError的原因
为什么函数内无参数exec赋值的变量会触发NameError
首先复现问题的代码如下:
def func(): exec("my_var = 42") print(my_var) func()
运行这段代码会抛出NameError,核心原因是Python函数的局部变量是编译期静态确定的,和exec动态修改名字空间的逻辑存在本质冲突,和官方文档描述的exec执行规则并不矛盾:
- Python在编译函数定义的阶段,会静态扫描整个函数体的所有语法,把所有在函数内显式出现在赋值语句左侧的变量名标记为局部变量。这些局部变量在运行时不会通过字典动态查找,而是通过栈帧里的fast locals数组按固定偏移量访问,访问效率远高于字典查找。
- 当你不给
exec()传入globals、locals参数时,exec确实会默认使用当前作用域的全局、局部名字空间来执行代码,但函数运行时传给exec的局部名字空间是一个临时生成的字典副本,不是和fast locals绑定的底层结构:exec执行my_var=42时,只是把这个键值对写到了临时字典里,根本没有修改fast locals数组。 - 对应代码里的
print(my_var)语句,编译器在扫描函数体的时候,根本看不到exec内部的动态赋值逻辑,没有发现任何显式给my_var赋值的语句,不会把它标记为局部变量,也不会生成从局部字典查找变量的逻辑,最终按规则查找时找不到对应变量,就抛出了NameError。
为什么显式传入全局参数可以正常运行
可正常执行的参考代码如下:
exec("my_var = 42", globals(), globals())
当你显式把全局名字空间同时作为exec执行的globals和locals参数时,exec内部的赋值逻辑会直接把my_var写入模块级的全局名字字典。
而编译器处理print(my_var)时,因为函数内没有对my_var的显式局部赋值,会把它标记为全局变量,运行时直接从全局名字字典里查找,刚好exec已经提前把变量写入了这个字典,所以可以正常访问。
补充:这个问题只会出现在函数内部。如果你在模块顶层直接写无参数的exec赋值,之后直接访问变量是完全正常的——因为模块级作用域没有fast locals的静态绑定机制,所有变量都是存在全局字典里动态查找的,不存在编译期确定偏移量的逻辑。
内容的提问来源于stack exchange,提问作者Manuel Ruiz
相关产品推荐
相关产品推荐

