在闭包中使用eval触发NameError的问题与解决方案
问题
全局环境下这段代码可以正常执行:
a = 1 fn = eval('lambda: a') fn()
但把代码放到函数闭包内后,会抛出NameError:
def x(): a = 1 fn = eval('lambda: a') fn() x()
报错信息:
NameError: name 'a' is not defined
解决方案
你已经找到的有效方法是给eval显式传入当前函数的局部命名空间:
def x(): a = 1 fn = eval('lambda: a', locals()) fn() x()
这个方案不会引入全局变量的副作用,是最贴合需求的解决方式。
为什么闭包场景下会有差异?
1. eval的默认命名空间规则
在全局作用域中,locals()和globals()指向同一个字典,eval默认会使用这个全局字典作为查找变量的命名空间,所以lambda能找到全局的a。
但在函数内部,eval默认只使用全局命名空间(即globals()的内容),不会自动去扫描当前函数的局部变量——因为函数的局部变量是存在栈帧的专用内存结构里,不是普通的可遍历字典,默认情况下eval不会去访问这部分内容。
2. 闭包变量的捕获逻辑
普通嵌套函数(直接写在代码里的lambda或def)会通过Python的闭包机制自动捕获外层函数的局部变量,这些变量会被保存在函数对象的__closure__属性中。但eval动态生成lambda时,如果你不指定局部命名空间,它只会绑定全局命名空间,不会触发闭包的变量捕获逻辑,所以运行lambda时只会在全局找a,自然找不到函数内的局部变量。
当你显式传入locals()给eval时,相当于强制让它在当前函数的局部命名空间中查找变量,此时生成的lambda就能正确绑定到函数内的a。
内容的提问来源于stack exchange,提问作者link89
相关产品推荐
相关产品推荐

