是否有任何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)
我知道它与locals和globals有关,好像我从我的主范围传递exec函数locals和globals它工作正常,但我不完全理解发生了什么。
这可能是Cython的错误吗?
编辑:用python 3.4.0和python 3.4.3
试试答案 0 :(得分:8)
问题是因为exec()
中的列表理解是无关闭的。
当你在exec()
之外创建一个函数(在这种情况下是一个列表推导)时,解析器使用自由变量构建一个元组(代码块使用但不是由它定义的变量,即。在你的情况下g
)。这个元组称为函数的闭包。它保存在函数的__closure__
成员中。
在exec()
中,解析器不会在列表解析上构建闭包,而是默认尝试查看globals()
字典。这就是为什么在代码开头添加global g
会起作用的原因(以及globals().update(locals())
)。
在其两个参数版本中使用exec()
也将解决问题:Python会将globals()和locals()字典合并为一个(根据the documentation)。执行赋值时,它同时在globals 和本地中完成。由于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()
时,无法实现其执行代码中定义的函数需要关闭。对于他来说,其字符串中不以缩进开头的代码位于全局范围内。知道他是否以需要关闭的方式调用的唯一方法是需要delete from classroom c
left join lesson_plan_classroom lpc
on c.id = lpc.classroom_id
left join response r on c.id = r.classroom_id
where r.classroom_id is null
and lpc.classroom_id is null
and c.enabled = 0;
来检查当前范围(这对我来说似乎很狡猾)。
这确实是一种模糊不清的行为,可以解释但当它发生时肯定会引起一些人的注意。这是the Python guide中很好解释的副作用,但很难理解为什么它适用于这种特殊情况。
我的所有分析都是在Python 3上进行的,我没有在Python 2上做过任何尝试。
答案 1 :(得分: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()
字典。这与优化的查找混淆。见,例如, this question and its answers
不应修改此词典的内容;更改可能不会影响解释器使用的本地和自由变量的值。
因为您在某个函数中调用了exec()
并且没有明确传入locals()
,所以您修改了函数的本地文件,并且正如文档所解释的那样,总是有效。
正如其他人所指出的那样,Pythonic方法是将映射对象显式传递给exec()。
Python 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,它将用于全局访问和本地访问(这与你的代码没有在函数或类定义中执行的情况完全相同 - 是< / em>没有locals()
)。因此,在这种情况下,与locals()
相关的错误不会出现。
上面的类示例是:
code = """
g = 5
x = [g for i in range(5)]
"""
class Foo(object):
exec(code, vars())
print(Foo.x, Foo.g)
答案 2 :(得分:-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)