I'm having trouble understanding the semantics of "eval()" and "exec" in Python. (All code in this question behaves the same way in Python 2.7.8 and Python 3.4.2). The documentation for "eval" says:
在Python中,我很难理解“eval()”和“exec”的语义。(这个问题中的所有代码在Python 2.7.8和Python 3.4.2中都是相同的方式)。“eval”的文档说明:
If both [locals and globals] are omitted, the expression is executed in the environment where eval() is called.
如果省略了(local和globals),则在调用eval()的环境中执行该表达式。
There is similar language for "exec". I clearly don't understand this sentence because I would expect the four functions defined by the following program to do the same thing.
“exec”也有类似的语言。我显然不理解这个句子,因为我期望下面的程序定义的四个函数也可以做同样的事情。
def h(x):
ls = locals()
exec('def i(y): return (w, x, y)', globals(), ls)
i = ls['i']
def j(y): return (w, x, y)
k = eval('lambda y: (w, x, y)')
l = lambda y: (w, x, y)
return i, j, k, l
w = 1
i, j, k, l = h(2)
They do not.
他们不。
>>> i(3)
Traceback (most recent call last):
File "", line 1, in
File "", line 1, in i
NameError: name 'x' is not defined
>>> j(3)
(1, 2, 3)
>>> k(3)
Traceback (most recent call last):
File "", line 1, in
File "", line 1, in
NameError: name 'x' is not defined
>>> l(3)
(1, 2, 3)
Disassembling the code reveals why: "x" is treated as a global variable by "eval" and "exec".
分解代码揭示了为什么:“x”被“eval”和“exec”当作一个全局变量。
from dis import dis
print("This is `i`:")
dis(i)
print("This is `j`:")
dis(j)
print("This is `k`:")
dis(k)
print("This is `l`:")
dis(l)
print("For reference, this is `h`:")
dis(h)
Output:
输出:
This is `i`:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `j`:
25 0 LOAD_GLOBAL 0 (w)
3 LOAD_DEREF 0 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `k`:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `l`:
27 0 LOAD_GLOBAL 0 (w)
3 LOAD_DEREF 0 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
For reference, this is `h`:
22 0 LOAD_NAME 0 (locals)
3 CALL_FUNCTION 0
6 STORE_FAST 1 (ls)
23 9 LOAD_CONST 1 ('def i(y): return (w, x, y)')
12 LOAD_NAME 1 (globals)
15 CALL_FUNCTION 0
18 LOAD_FAST 1 (ls)
21 EXEC_STMT
24 22 LOAD_FAST 1 (ls)
25 LOAD_CONST 2 ('i')
28 BINARY_SUBSCR
29 STORE_FAST 2 (i)
25 32 LOAD_CLOSURE 0 (x)
35 BUILD_TUPLE 1
38 LOAD_CONST 3 ()
41 MAKE_CLOSURE 0
44 STORE_FAST 3 (j)
26 47 LOAD_NAME 2 (eval)
50 LOAD_CONST 4 ('lambda y: (w, x, y)')
53 CALL_FUNCTION 1
56 STORE_FAST 4 (k)
27 59 LOAD_CLOSURE 0 (x)
62 BUILD_TUPLE 1
65 LOAD_CONST 5 ( at 0x7ffc3843c3b0, file "test.py", line 27>)
68 MAKE_CLOSURE 0
71 STORE_FAST 5 (l)
28 74 LOAD_FAST 2 (i)
77 LOAD_FAST 3 (j)
80 LOAD_FAST 4 (k)
83 LOAD_FAST 5 (l)
86 BUILD_TUPLE 4
89 RETURN_VALUE
"j" and "l" above have the behaviour I want. How can I get this behaviour using "eval" or "exec"?
“j”和“l”上面有我想要的行为。如何使用“eval”或“exec”来获得这种行为?
Using a class instead of a function as the outer wrapper does change the semantics, but in the opposite to the desired way. It makes "x" into a global.
使用类而不是函数作为外部包装器确实改变了语义,但与所期望的方式相反。它使“x”成为一个全局变量。
class H:
x = 2
f = staticmethod(eval('lambda y: (w, x, y)'))
H.dis(H.f)
w = 1
H.f(3)
Output:
输出:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
Traceback (most recent call last):
File "", line 1, in
File "", line 1, in
NameError: global name 'x' is not defined
Wrapping in "classmethod" or leaving it as an unbound instance method just makes things worse.
在“classmethod”中进行包装或将其作为未绑定的实例方法进行包装,只会使事情变得更糟。
Substituting "x" using string interpolation works for integers:
用字符串插值代替“x”的整数:
def h(x):
return eval('lambda y: (w, %r, y)' % x)
k = h(2)
dis(k)
w = 1
k(3)
Output:
输出:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_CONST 1 (2)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
(1, 2, 3)
However, I don't want to assume that "x" can be losslessly converted to a string and back. The attempt is broken in the following examples:
但是,我不想假定“x”可以被无损地转换成字符串并返回。尝试在以下例子中被打破:
k = h(lambda: "something")
k = h(open('some_file', 'w'))
cell = ["Wrong value"]
k = h(cell)
cell[0] = "Right value"
k(3)
Since Python is looking for a global variable, one obvious attempt is to pass "x" as a global variable:
由于Python正在寻找一个全局变量,一个明显的尝试是将“x”作为一个全局变量传递:
def h(x):
my_globals = {'w': w, 'x': x}
return eval('lambda y: (w, x, y)', my_globals)
k = h(2)
dis(k)
w = 1
k(3)
Output:
输出:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
(1, 2, 3)
This attempt is broken because it reads the value of "w" too early:
这种尝试之所以失败,是因为它过早地读取了“w”的值:
w = "Wrong value"
k = h(2)
w = "Right value"
k(3)
I did eventually find an approach that works, but I really don't like it:
我最终找到了一种可行的方法,但我真的不喜欢它:
def h(x):
return eval('lambda x: lambda y: (w, x, y)')(x)
k = h(2)
dis(k)
w = 1
k(3)
Output:
输出:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_DEREF 0 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
(1, 2, 3)
In particular, this is going to become painful if I do not know the full list of local variables captured by the string I'm passing to "eval".
特别是,如果我不知道我传递给“eval”的字符串所捕获的局部变量的完整列表,这将会变得很痛苦。
Can you do better?
你能做得更好吗?
Update 2014-12-25
更新2014-12-25
Looking for more ways of creating the local variable "x", I tried this:
为了寻找创建局部变量x的更多方法,我尝试了以下方法:
def h(x):
ls = locals()
exec('x = x\ndef i(y): return (w, x, y)', globals(), ls)
exec('_ = x\ndef j(y): return (w, x, y)', globals(), ls)
return ls['i'], ls['j'], ls['_'], ls['x']
i, j, check1, check2 = h(2)
assert check1 == 2
assert check2 == 2
w = 1
print("This is `i`:")
dis(i)
print("This is `j`:")
dis(j)
print("i(3) = %r" % (i(3),))
print("j(3) = %r" % (j(3),))
The extra assignment to "x" has no effect. The assertions verify that "x" is in the dictionary of locals, but it is not captured by the lambdas. Here is the output:
额外分配给“x”没有效果。断言验证“x”在本地字典中,但它没有被lambdas捕获。这是输出:
This is `i`:
2 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `j`:
2 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
The calls to "i" and "j" both crash, complaining that there is no global variable "x".
对“i”和“j”的调用都崩溃了,抱怨没有全局变量“x”。
[Edit 2014-12-29: This succeeds only on Python 3.]
[编辑2014-12-29:只有在Python 3中才能成功。]
Another way of creating a local variable is like this:
创建局部变量的另一种方法是:
def h(x):
i = eval('[lambda y: (w, x, y) for x in [x]][0]')
j = eval('[lambda y: (w, x, y) for _ in [x]][0]')
return i, j
i, j = h(2)
w = 1
print("This is `i`:")
dis(i)
print("This is `j`:")
dis(j)
print("i(3) = %r" % (i(3),))
print("j(3) = %r" % (j(3),))
Strangely, in this case the extra assignment to "x" does have an effect. This does work, i.e. "i" is different from "j". Here is the output:
奇怪的是,在这种情况下,“x”的额外赋值确实有效果。这工作,即。“i”与“j”不同。这是输出:
This is `i`:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_DEREF 0 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
This is `j`:
1 0 LOAD_GLOBAL 0 (w)
3 LOAD_GLOBAL 1 (x)
6 LOAD_FAST 0 (y)
9 BUILD_TUPLE 3
12 RETURN_VALUE
i(3) = (1, 2, 3)
The call to "j" crashes, complaining that there is no global "x", but "i" works as desired and has the correct bytecode.
调用“j”崩溃,抱怨没有全局的“x”,但是“i”按需要工作,并且有正确的字节码。
Why does this work, while "Failure 4" above does not? What is the rule that determines whether the local "x" can be captured? And what is the history of this design? (It seems absurd!)
为什么这个工作,而“失败4”却没有?决定本地“x”是否可以被捕获的规则是什么?这个设计的历史是什么?(看来荒谬的!)
1
I think you want your created functions to inherit the local environment of the function that creates them, but also the real global environment (of the function that creates them). That's why you don't like them referring to x as a global, right?
我认为您希望您创建的函数继承创建它们的函数的本地环境,但也需要真正的全局环境(创建它们的函数)。这就是为什么你不喜欢他们把x作为一个全局变量,对吧?
The following creates a "wrapper" function around the desired function, all within the same exec string. The values of the creating function's locals are passed in when you call or re-call the wrapper, creating a new wrapped closure.
下面创建一个围绕所需函数的“包装器”函数,所有这些函数都在同一个exec字符串中。当您调用或重新调用包装器时,创建函数的本地值会传入,从而创建一个新的包装闭包。
The code is sensitive to new variables being created in the locals context. It goes to some trouble to make sure that the function and wrapper names are both known and have values there.
代码对在本地环境中创建的新变量很敏感。要确保函数和包装器的名称都是已知的,并且在那里有值,这就有点麻烦了。
def wrap_f(code, gs, ls, wrapper_name, function_name):
ls[function_name] = ls[wrapper_name] = None
arglist = ",".join(ls.keys())
wrapcode = """
def {wrapper_name}({arglist}):
{code}
return {function_name}
""".format(wrapper_name=wrapper_name, arglist=arglist,
code=code, function_name=function_name)
exec(wrapcode, gs, ls)
wrapper = ls[wrapper_name]
return wrapper, wrapper(**ls)
So, to answer the original question, this code...
所以,为了回答最初的问题,这段代码…
def h(x):
mcode = " def m(y): return (w, x, y)" # must be indented 4 spaces.
mwrap, m = wrap_f(mcode, globals(), locals(), "mwrap", "m")
return m
w = 1
m = h(2)
print m(3)
...produces this output:
…产生该输出:
(1, 2, 3)
And this example shows what to do when the locals in the creator function change:
这个例子展示了当创作者的功能改变时该怎么做:
def foo(x):
barleycode = """
def barley(y):
print "barley's x =", x
print "barley's y =", y
"""
barleywrap, barley = wrap_f(barleycode, globals(), locals(),
"barleywrap", "barley")
barley("this string")
print
x = "modified x"
barley = barleywrap(**locals())
barley("new arg")
print
x = "re-modified x"
barley("doesn't see the re-mod.")
x = "the global x"
foo("outer arg")
This produces the output:
这个生成的输出:
barley's x = outer arg
barley's y = this string
barley's x = modified x
barley's y = new arg
barley's x = modified x
barley's y = doesn't see the re-mod.
0
I'm not sure I quite got it myself, but I'll try my best: I guess when you run the eval/exec python does not understand that it was inside the function, I don't really know why. What I'll try to do is using format string like this
我不确定我自己是否有,但我会尽我最大的努力:我猜当你运行eval/exec python时,我不知道它在函数内部,我不知道为什么。我要做的是使用这样的格式字符串!
k = eval("lambda y: (w, {0}, y)".format(x))
I'm not sure if this thing works though. Also, why do you need to use eval and exec this way?
我不确定这个东西是否有用。另外,为什么需要使用eval和exec ?