为何eval函数中的lambda无法捕获自定义locals字典中的变量?
你遇到的这个问题,本质是Python函数(包括lambda)的变量解析机制和eval函数的参数行为共同作用的结果,咱们来一步步理清楚:
先明确核心现象
你尝试通过eval定义lambda时,将y放在locals参数中,调用lambda时会触发NameError:
>>> x = eval('lambda: print(y)', {}, {'y':2}) >>> x() Traceback (most recent call last): File "<pyshell#75>", line 1, in <module> x() File "<string>", line 1, in <lambda> NameError: name 'y' is not defined
但如果把y放在globals参数里,lambda就能正常运行:
>>> x = eval('lambda: print(y)', {'y': 2}, {}) >>> x() 2
背后的关键原因:lambda绑定的是eval的globals命名空间
Python中的函数(包括lambda表达式)在创建时,会自动绑定当前的全局命名空间作为自己的__globals__属性。而eval函数的globals参数,就是用来指定执行表达式时的全局命名空间——这个空间会被创建的lambda直接捕获为自身的__globals__。
而eval的locals参数只是一个临时的变量字典,仅在eval执行表达式的瞬间生效,用来解析表达式中的变量引用,但lambda作为一个独立的函数对象,并不会把这个临时的locals字典保存下来。当你后续调用lambda时,它会遵循Python的变量查找顺序:
- 首先查找自身的局部变量(这里lambda没有定义局部变量)
- 然后查找自己绑定的
__globals__命名空间 - 最后查找内置命名空间
所以当y只存在于eval的locals参数中时,lambda的__globals__里没有y,自然会报错;而把y放在globals里时,lambda的__globals__包含了这个变量,调用时就能正常找到。
验证结论:查看lambda的__globals__属性
你可以直接打印lambda的__globals__来确认这一点:
>>> x1 = eval('lambda: print(y)', {}, {'y':2}) >>> print(x1.__globals__) {'__builtins__': <module 'builtins' (built-in)>} # 没有y的踪迹 >>> x2 = eval('lambda: print(y)', {'y':2}, {}) >>> print(x2.__globals__) {'y': 2, '__builtins__': <module 'builtins' (built-in)>} # 明确包含y
补充:如何让lambda“捕获”locals里的变量?
如果你想让lambda获取eval的locals参数里的变量,可以用默认参数固化的技巧,在lambda定义时把变量值绑定到参数默认值上:
>>> x = eval('lambda y=y: print(y)', {}, {'y':2}) >>> x() 2
这里lambda的默认参数y=y会在eval执行时(也就是lambda创建时)从locals字典中取到y=2,并把这个值固定下来,后续调用时就不再依赖全局命名空间了。
内容的提问来源于stack exchange,提问作者user8426421

