是否有任何 python 专家能够解释为什么这段代码不起作用:
def f(code_str):
exec(code_str)
code = """
g = 5
x = [g for i in range(5)]
"""
f(code)
错误:
Traceback (most recent call last):
File "py_exec_test.py", line 9, in <module>
f(code)
File "py_exec_test.py", line 2, in f
exec(code_str)
File "<string>", line 3, in <module>
File "<string>", line 3, in <listcomp>
NameError: name 'g' is not defined
虽然这个工作正常:
code = """
g = 5
x = [g for i in range(5)]
"""
exec(code)
我知道它与局部变量和全局变量有关,就好像我从我的主作用域向 exec 函数传递局部变量和全局变量一样,它工作正常,但我不完全明白发生了什么。
这可能是 Cython 的错误吗?
编辑:用 python 3.4.0 和 python 3.4.3 试过了
问题是因为列表理解在 exec()
中是无闭包的。
当您在 exec()
之外创建函数(在本例中为列表理解)时,解析器会构建一个包含自由变量(代码块使用但 undefined variable )的元组通过它,即 g
在你的情况下)。这个元组称为函数的闭包。它保存在函数的 __closure__
成员中。
在 exec()
中时,解析器不会在列表理解上构建闭包,而是默认尝试查看 globals()
字典。这就是为什么在代码的开头添加 global g
会起作用(以及 globals().update(locals())
)。
在其两个参数版本中使用 exec()
也可以解决问题:Python 会将 globals() 和 locals() 字典合并为一个字典(根据 the documentation )。执行赋值时,会同时在全局 和 局部中完成。由于 Python 将检查全局变量,因此这种方法可行。
这是对这个问题的另一种看法:
import dis
code = """
g = 5
x = [g for i in range(5)]
"""
a = compile(code, '<test_module>', 'exec')
dis.dis(a)
print("###")
dis.dis(a.co_consts[1])
此代码生成此字节码:
2 0 LOAD_CONST 0 (5)
3 STORE_NAME 0 (g)
3 6 LOAD_CONST 1 (<code object <listcomp> at 0x7fb1b22ceb70, file "<boum>", line 3>)
9 LOAD_CONST 2 ('<listcomp>')
12 MAKE_FUNCTION 0
15 LOAD_NAME 1 (range)
18 LOAD_CONST 0 (5)
21 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
24 GET_ITER
25 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
28 STORE_NAME 2 (x)
31 LOAD_CONST 3 (None)
34 RETURN_VALUE
###
3 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_GLOBAL 0 (g) <---- THIS LINE
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
注意它如何执行 LOAD_GLOBAL
以在最后加载 g
。
现在,如果您有此代码:
def Foo():
a = compile(code, '<boum>', 'exec')
dis.dis(a)
print("###")
dis.dis(a.co_consts[1])
exec(code)
Foo()
这将提供完全相同的字节码,这是有问题的:因为我们在一个函数中,g
不会在全局变量中声明,而是在函数的局部变量中声明。但是 Python 试图在全局变量中搜索它(使用 LOAD_GLOBAL
)!
这是解释器在 exec()
之外所做的事情:
def Bar():
g = 5
x = [g for i in range(5)]
dis.dis(Bar)
print("###")
dis.dis(Bar.__code__.co_consts[2])
这段代码给了我们这个字节码:
30 0 LOAD_CONST 1 (5)
3 STORE_DEREF 0 (g)
31 6 LOAD_CLOSURE 0 (g)
9 BUILD_TUPLE 1
12 LOAD_CONST 2 (<code object <listcomp> at 0x7fb1b22ae030, file "test.py", line 31>)
15 LOAD_CONST 3 ('Bar.<locals>.<listcomp>')
18 MAKE_CLOSURE 0
21 LOAD_GLOBAL 0 (range)
24 LOAD_CONST 1 (5)
27 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
30 GET_ITER
31 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
34 STORE_FAST 0 (x)
37 LOAD_CONST 0 (None)
40 RETURN_VALUE
###
31 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 12 (to 21)
9 STORE_FAST 1 (i)
12 LOAD_DEREF 0 (g) <---- THIS LINE
15 LIST_APPEND 2
18 JUMP_ABSOLUTE 6
>> 21 RETURN_VALUE
如您所见,g
是使用 LOAD_DEREF
加载的,在 BUILD_TUPLE
中生成的元组中可用,该元组加载了变量 g
使用 LOAD_CLOSURE
。 MAKE_CLOSURE
语句创建一个函数,就像前面看到的 MAKE_FUNCTION
一样,但是有一个闭包。
这是我对这种方式的原因的猜测:闭包是在第一次读取模块时在需要时创建的。当 exec()
被执行时,它无法意识到在其执行的代码中定义的函数需要关闭。对他来说,其字符串中不以缩进开头的代码在全局范围内。要知道他是否以需要关闭的方式被调用的唯一方法是需要 exec()
检查当前范围(这对我来说似乎很老套)。
这确实是一个晦涩的行为,可以解释,但当它发生时肯定会引起一些人的注意。这是一个在 the Python guide 中得到很好解释的副作用。 ,尽管很难理解为什么它适用于这种特殊情况。
我所有的分析都是在 Python 3 上进行的,我没有在 Python 2 上尝试过任何东西。