10

是否有任何 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 试过这个

4

3 回答 3

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])

此代码生成此字节码:

  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加载变量。该语句创建了一个函数,就像前面看到的一样,但带有一个闭包。gLOAD_CLOSUREMAKE_CLOSUREMAKE_FUNCTION

这是我对这种方式原因的猜测:第一次读取模块时,需要时创建闭包。执行时exec(),无法实现其执行代码中定义的功能需要闭包。对他来说,其字符串中不以缩进开头的代码在全局范围内。知道他是否以需要闭包的方式调用的唯一方法是exec()检查当前范围(这对我来说似乎很骇人听闻)。

这确实是一种晦涩难懂的行为,可以解释,但当它发生时肯定会引起一些人的注意。这是Python 指南中很好解释的副作用,尽管很难理解为什么它适用于这种特殊情况。

我所有的分析都是在 Python 3 上进行的,我没有在 Python 2 上尝试过任何东西。

于 2015-10-01T21:24:54.667 回答
2

编辑 2

正如其他评论者所注意到的,您似乎在 Python 3 中发现了一个错误(在 2.7 中对我来说没有发生)。

正如这个答案下面的评论中所讨论的,原始代码:

def f(code_str):
    exec(code_str)

在功能上等同于:

def f(code_str):
    exec(code_str, globals(), locals())

在我的机器上,运行 3.4 它在功能上等同于它会爆炸的程度。这里的错误与在有两个映射对象时运行列表推导有关。例如:

def f(code_str):
    exec(code_str, globals(), {})

也会因相同的异常而失败。

为避免引发此错误,您必须只传递一个映射对象(因为不传递任何等于传递两个),并确保它在所有情况下都能正常工作,您永远不应将函数locals()作为该映射对象传递。

这个答案的其余部分是在我意识到 3 岁以下的行为不同之前写的。我要离开它,因为它仍然是很好的建议,并且对 exec 行为提供了一些见解。

永远不应该直接更改函数的locals()字典。这与优化的查找相混淆。参见,例如这个问题及其答案

特别是,正如Python 文档所解释的:

不得修改本词典的内容;更改可能不会影响解释器使用的局部变量和自由变量的值。

因为您exec()从函数内部调用并且没有显式传入locals(),所以您修改了函数的局部变量,并且正如文档解释的那样,这并不总是有效。

因此,正如其他人所指出的,Pythonic 方式是将映射对象显式传递给 exec()。

蟒蛇 2.7

什么时候可以修改locals()?一个答案是当你正在构建一个类时——那时它只是另一本字典:

code = """
g = 5
x = [g for i in range(5)]
"""

class Foo(object):
    exec(code)

print Foo.x, Foo.g

[5, 5, 5, 5, 5] 5

编辑——Python 3正如其他人指出的那样,这里似乎存在一个错误,与locals()您是否在函数内无关。您可以通过为全局变量传递一个参数来解决此问题。Python 文档解释说,如果您只传递一个 dict,它将用于全局和本地访问(这实际上与您的代码没有在函数或类定义中执行一样——没有locals(). locals()所以在这种情况下不会出现与相关的错误。

上面的类示例将是:

code = """
g = 5
x = [g for i in range(5)]
"""

class Foo(object):
    exec(code, vars())

print(Foo.x, Foo.g)
于 2015-10-01T19:47:56.937 回答
-2

好的!做了一些查看,看起来您的行x = [g for i in range(5)]正在尝试创建一个新的未初始化值g,而不是使用您之前定义的值。

pythonic 修复方法是将您的范围传递给您exec(),如下所示:

def f(code,globals,locals):
    exec(code,globals,locals)

code = """
g = 5
x = [g for i in range(5)]
print(x)
"""

f(code,globals(),locals())

这是一个非常好的问题。我从回答中学到了很多。

参考这个以获取更多信息exec()https ://docs.python.org/3/library/functions.html#exec

@Pynchia 建议了一个缩短版本,并定义了函数内globals()调用的时间。exec()

def f(code):
    exec(code,globals())

code = """
g = 5
x = [g for i in range(5)]
print(x)
"""

f(code)
于 2015-10-01T19:32:46.263 回答