Python 2.7+版本exec()函数异常:局部变量可查却无法直接访问
这是个典型的Python 2到3过渡时的作用域行为差异问题,核心在于Python 3对函数局部作用域的编译优化逻辑,和Python 2完全不一样。
为什么会出现这种矛盾的现象?
1. Python 3对局部变量的静态“提前锁定”
在Python 3里,当你定义一个函数时,解释器会提前扫描整个函数体的代码,把所有能明确识别的赋值变量(比如写死的x = 5)标记为“局部变量”,并在函数的字节码里预分配好它们的存储位置。而exec()是动态执行字符串代码,它创建的变量不在这个预扫描的列表里——简单说,解释器根本没把这些动态创建的变量当成“合法”的局部变量,所以你直接写print(foo)的时候,它会认为你在引用一个没定义的局部变量,直接抛NameError。
2. locals()是“快照”而非真实作用域
函数里调用locals()时,返回的是一个当前局部作用域的临时快照字典,它会把动态添加的变量也包含进来,但这个字典和函数实际的局部变量栈帧是分开的。你可以从字典里读到这些变量,但解释器不会把字典里的内容当成真正的局部变量来处理,所以直接访问就会失败。
3. Python 2和3的本质差异
在Python 2中,函数内的locals()返回的是真实的局部作用域字典——修改这个字典(包括通过exec()加变量)会直接同步到局部作用域里,所以直接访问变量没问题。但Python 3为了提升函数执行性能,放弃了这种动态的作用域处理,改用静态分析的方式,代价就是牺牲了这种动态添加局部变量的灵活性。
4. 全局作用域为什么不受影响?
在全局作用域里,locals()和globals()是同一个字典,exec()动态添加的变量会直接写入这个全局字典,解释器访问全局变量时是直接查这个字典的,所以不会有问题。
怎么解决这个问题?
如果需要在函数内通过exec()创建可直接访问的变量,有几种可行的方案:
方法1:给exec指定自定义的局部作用域字典
你可以自己创建一个字典,传给exec()作为局部作用域,之后从这个字典里读取变量:
def func2(): varName = 'bar' varValue = 42 localVarToEvaluate = varName + ' = varValue' # 先复制当前局部变量到自定义字典(可选,保留已有变量) local_vars = locals().copy() try: # 把自定义字典作为exec的局部作用域 exec(localVarToEvaluate, globals(), local_vars) except Exception as err: print(str(err)) # 从自定义字典里访问动态创建的变量 if 'bar' in local_vars: print("'bar' OK:", local_vars['bar']) func2()
方法2:直接用全局作用域(谨慎使用)
如果变量不需要是函数局部的,可以让exec()直接写入全局作用域:
def func2(): varName = 'bar' varValue = 42 localVarToEvaluate = varName + ' = varValue' try: # 只传globals(),exec会把变量写到全局 exec(localVarToEvaluate, globals()) except Exception as err: print(str(err)) if 'bar' in globals(): print("'bar' OK:", bar) func2()
方法3:用自定义容器存储变量(更优雅)
其实最好的方式是避免直接操作作用域,用字典或类实例来管理这些动态变量:
def func2(): # 用字典当变量容器 var_container = {} varName = 'bar' varValue = 42 # 直接赋值或者用exec指定容器字典 var_container[varName] = varValue # 或者用exec: exec(f"{varName} = varValue", globals(), var_container) print("'bar' OK:", var_container['bar']) func2()
内容的提问来源于stack exchange,提问作者schperplata

