【发布时间】:2014-12-24 14:46:06
【问题描述】:
我无法理解 Python 中“eval()”和“exec”的语义。 (此问题中的所有代码在 Python 2.7.8 和 Python 3.4.2 中的行为方式相同)。 “eval”的documentation 表示:
如果 [locals 和 globals] 都省略,则执行表达式 在调用 eval() 的环境中。
“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)
他们没有。
>>> i(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in i
NameError: name 'x' is not defined
>>> j(3)
(1, 2, 3)
>>> k(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 1, in <lambda>
NameError: name 'x' is not defined
>>> l(3)
(1, 2, 3)
反汇编代码揭示了原因:“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)
输出:
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 (<code object j at 0x7ffc3843c030, file "test.py", line 25>)
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 (<code object <lambda> 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”和“l”上面有我想要的行为。如何使用“eval”或“exec”获得这种行为?
失败 1
使用类而不是函数作为外包装确实会改变语义,但与期望的方式相反。它将“x”变成一个全局变量。
class H:
x = 2
f = staticmethod(eval('lambda y: (w, x, y)'))
H.dis(H.f)
w = 1
H.f(3)
输出:
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 "<stdin>", line 1, in <module>
File "<string>", line 1, in <lambda>
NameError: global name 'x' is not defined
包装在“classmethod”中或将其保留为未绑定的实例方法只会让事情变得更糟。
失败 2
使用字符串插值替换“x”对整数有效:
def h(x):
return eval('lambda y: (w, %r, y)' % x)
k = h(2)
dis(k)
w = 1
k(3)
输出:
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)
但是,我不想假设“x”可以无损地转换为字符串并返回。尝试在以下示例中被破坏:
k = h(lambda: "something")
k = h(open('some_file', 'w'))
cell = ["Wrong value"]
k = h(cell)
cell[0] = "Right value"
k(3)
失败 3
由于 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)
输出:
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)
这个尝试被破坏了,因为它过早地读取了“w”的值:
w = "Wrong value"
k = h(2)
w = "Right value"
k(3)
成功1
我最终确实找到了一种可行的方法,但我真的不喜欢它:
def h(x):
return eval('lambda x: lambda y: (w, x, y)')(x)
k = h(2)
dis(k)
w = 1
k(3)
输出:
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)
尤其是,如果我不知道传递给“eval”的字符串捕获的局部变量的完整列表,这将变得很痛苦。
你能做得更好吗?
2014 年 12 月 25 日更新
失败 4
寻找更多创建局部变量“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),))
对“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
对“i”和“j”的调用都崩溃了,抱怨没有全局变量“x”。
成功2
[编辑 2014-12-29:这仅在 Python 3 上成功。]
另一种创建局部变量的方法是这样的:
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),))
奇怪的是,在这种情况下,对“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)
对“j”的调用崩溃,抱怨没有全局“x”,但“i”按预期工作并且具有正确的字节码。
为什么这行得通,而上面的“失败 4”却行不通?确定本地“x”是否可以被捕获的规则是什么?这种设计的历史是什么? (这似乎很荒谬!)
【问题讨论】:
-
i和j不是本地人。它们是闭包。 Python 在编译时确定闭包,但在eval()中产生这种闭包的唯一方法是您已经发现的方式。您不能以任何其他方式创建闭包。 -
令人惊讶的是,“locals”中传递的变量无法被捕获。这是为什么呢?
-
因为闭包需要更多的脚手架;变量的生命周期超出了函数的生命周期,并且常规的局部变量不会在那里。
-
对,您找到了另一种创建新范围的方法;列表推导(以及 dict 和集合推导以及生成器表达式)也允许您在单个表达式中创建父范围。
-
您也可以使用默认参数破解。在 PEP 227 将词法作用域添加到 Python 2.1 之前,这很常见。您可以使用 3.x 仅关键字参数使其更可靠:
k = eval('lambda y,*,x=x: (w,x,y)')。
标签: python python-2.7 eval python-3.4