Python中变量存在于locals()却无法在当前局部作用域访问
问题原因
这个现象是CPython解释器对函数局部变量的静态编译优化机制导致的,核心逻辑如下:
- 编译期的局部变量确定规则
Python在编译函数代码时,会静态扫描整个函数体内所有显式声明、赋值的变量,将这些变量标记为局部变量,运行时对局部变量的访问是通过栈帧上固定的偏移位置直接读取的,不会动态查询locals()返回的字典。
示例中g()函数的代码里,除了exec内的字符串代码,没有任何地方显式定义或赋值过变量b,编译器编译阶段根本不会将b识别为该函数的局部变量,不会为它分配栈帧上的存储槽位。 exec()的实际写入位置
函数内调用exec()且没有手动指定命名空间参数时,exec()会默认使用当前函数的locals()返回字典作为代码执行的局部命名空间,因此执行exec("b=1")时,确实将b:1这个键值对写入了locals()字典中,这也是为什么你能通过locals()['b']成功取出值1。- 直接访问报错的触发逻辑
当代码执行到print(b)时,解释器按照编译阶段确定的规则查找b:首先检查当前函数的局部变量槽位,没有找到b;接着去全局命名空间、内置命名空间查找,依然没有找到b,最终抛出NameError。
补充说明:这个现象仅出现在函数作用域中。如果在模块全局作用域执行相同的
exec("b=1")再直接访问b是可以正常运行的——因为全局变量的访问本身就是动态查询globals()字典实现的,没有编译期静态绑定局部变量槽位的优化。
另外官方文档明确标注:不建议手动修改locals()返回的字典,修改后的行为在不同解释器、不同版本中不做一致性保证,CPython中函数内的locals()字典本质是栈帧局部变量的快照,对字典的修改不会反向同步到栈帧的局部变量槽位中。
复现代码与运行结果
复现代码:
def g(): print("Locals:", locals().keys()) exec("b=1") print("Locals:", locals().keys()) print(locals()['b']) print(b) g()
运行输出:
Locals: dict_keys([]) Locals: dict_keys(['b']) 1 Traceback (most recent call last): File "test.py", line 10, in <module> g() File "test.py", line 8, in g print(b) NameError: name 'b' is not defined
可行的规避方案
如果需要在函数内通过exec动态生成变量后访问,有两种通用方案:
- 提前在函数内显式声明变量,让编译器将其识别为局部变量:
def g(): b = None # 提前声明,编译器会为b分配局部变量槽位 exec("b=1") print(b) # 正常输出1 - 给
exec()传入独立的命名空间字典,不要依赖函数局部命名空间:def g(): ns = {} exec("b=1", ns) print(ns['b']) # 正常输出1
内容的提问来源于stack exchange,提问作者Frank
相关产品推荐
相关产品推荐

