为什么在exec内部闭包会失效?

19

在Python 2.6中,

>>> exec "print (lambda: a)()" in dict(a=2), {}
2
>>> exec "print (lambda: a)()" in globals(), {'a': 2}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
  File "<string>", line 1, in <lambda>
NameError: global name 'a' is not defined
>>> exec "print (lambda: a).__closure__" in globals(), {'a': 2}
None

我期望它会打印两次2,然后打印一个只包含一个cell的元组。在3.1中也是同样的情况。发生了什么事?

1个回答

27

当您向 execeval 传递一个字符串时,它会在考虑全局或本地变量之前将该字符串编译为代码对象。因此,当您这样说:

eval('lambda: a', ...)

它的意思是:

eval(compile('lambda: a', '<stdin>', 'eval'), ...)

编译器无法知道 a 是自由变量,因此将其编译为全局引用:

>>> c= compile('lambda: a', '<stdin>', 'eval')
>>> c.co_consts[0]
<code object <lambda> at 0x7f36577330a8, file "<stdin>", line 1>
>>> dis.dis(c.co_consts[0])
  1           0 LOAD_GLOBAL              0 (a)
              3 RETURN_VALUE        
因此,要使其正常工作,您需要将a放入全局而不是本地变量中。
是的,这有点不可靠。但这就是execeval的特点...它们不应该是友好的。

+1. 自从这个帖子发布以来,我每分钟都在刷新它,想要找到这个问题的答案。多亏了你,现在我可以暂时离开电脑出去享受阳光了。谢谢你!;) - Mia Clarke
为了让闭包与exec一起工作,我在这个答案中提供了一个技巧。简而言之,将定义函数的原始代码用临时辅助函数包装起来,以便compile可以识别像a这样的自由变量。 - hellohawaii
1
新的 closure 参数是否与解决这个问题有关?请参见 https://dev59.com/BF7Ms4gBPY-HTNNjtH9l。 - Barmar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接