lev*_*que 10 python list-comprehension python-exec
是否有任何python大师能够解释为什么这段代码不起作用:
def f(code_str):
exec(code_str)
code = """
g = 5
x = [g for i in range(5)]
"""
f(code)
Run Code Online (Sandbox Code Playgroud)
错误:
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
Run Code Online (Sandbox Code Playgroud)
虽然这个工作正常:
code = """
g = 5
x = [g for i in range(5)]
"""
exec(code)
Run Code Online (Sandbox Code Playgroud)
我知道它与locals和globals有关,就好像我从我的主范围传递exec函数locals和globals它工作正常,但我不完全理解发生了什么.
这可能是Cython的一个错误吗?
编辑:尝试使用python 3.4.0和python 3.4.3
问题是因为列表理解是无关紧要的exec().
当你在一个函数之外创建一个函数(在这种情况下是一个列表exec()解析)时,解析器使用自由变量构建一个元组(代码块使用的变量但不是由它定义的,即g在你的情况下).这个元组称为函数的闭包.它保存在__closure__函数的成员中.
在此时exec(),解析器不会在列表推导上构建闭包,而是默认尝试查看globals()字典.这就是为什么global g在代码的开头添加将工作(以及globals().update(locals())).
使用exec()其两个参数版本也将解决问题:Python将globals()和locals()字典合并为一个(根据文档).执行赋值时,它同时在全局变量和局部变量中完成.由于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])
Run Code Online (Sandbox Code Playgroud)
此代码生成此字节码:
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
Run Code Online (Sandbox Code Playgroud)
注意它最后如何执行LOAD_GLOBAL加载g.
现在,如果你有这个代码:
def Foo():
a = compile(code, '<boum>', 'exec')
dis.dis(a)
print("###")
dis.dis(a.co_consts[1])
exec(code)
Foo()
Run Code Online (Sandbox Code Playgroud)
这将提供完全相同的字节码,这是有问题的:因为我们在函数中,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])
Run Code Online (Sandbox Code Playgroud)
这段代码给了我们这个字节码:
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
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,g使用加载变量时使用LOAD_DEREF的元组中BUILD_TUPLE加载变量.该语句创建了一个函数,就像之前看到的那样,但是有一个闭包.gLOAD_CLOSUREMAKE_CLOSUREMAKE_FUNCTION
这是我对这种方式的猜测:在第一次读取模块时需要时创建闭包.当exec()被执行时,它是不能够实现其执行的代码中定义需要闭合的功能.对于他来说,其字符串中不以缩进开头的代码位于全局范围内.知道他是否以需要关闭的方式调用的唯一方法是需要exec()检查当前范围(这对我来说似乎很苛刻).
这确实是一种不起眼的行为,可以解释但当它发生时肯定会引起一些人的注意.这是Python指南中很好解释的副作用,但很难理解它为什么适用于这种特殊情况.
我的所有分析都是在Python 3上进行的,我没有在Python 2上做过任何尝试.
| 归档时间: |
|
| 查看次数: |
389 次 |
| 最近记录: |