如果嵌套在函数中,listcomp无法访问由exec调用的代码中定义的本地

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

Sor*_*vux 8

问题是因为列表理解是无关紧要的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上做过任何尝试.