python - Python 中 "eval"中捕获的变量

标签 python python-2.7 eval python-3.4

我无法理解 Python 中“eval()”和“exec”的语义。 (此问题中的所有代码在 Python 2.7.8 和 Python 3.4.2 中的行为方式相同)。 documentation对于“评估”说:

If both [locals and globals] are omitted, the expression is executed in the environment where eval() is called.

“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

包装在“类方法”中或将其保留为未绑定(bind)的实例方法只会让事情变得更糟。

失败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”在本地字典中,但它没有被 lambda 捕获。这是输出:

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”能否被捕获的规则是什么?这个设计的历史是什么? (这似乎很荒谬!)

最佳答案

我认为您希望您创建的函数继承创建它们的函数的局部环境,但真正的全局环境(创建它们的函数)。这就是为什么您不喜欢他们将 x 称为全局变量,对吧?

下面围绕所需函数创建了一个“包装器”函数,所有函数都在同一个 exec 字符串中。当您调用或重新调用包装器时,创建函数的局部值将传入,从而创建一个新的包装闭包。

代码对在本地上下文中创建的新变量很敏感。确保函数和包装器名称都是已知的并且在其中具有值会遇到一些麻烦。

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)

所以,为了回答最初的问题,这段代码...

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)

...产生这个输出:

(1, 2, 3)

这个例子展示了当 creator 函数中的局部变量发生变化时要做什么:

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

这会产生输出:

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.

关于python - Python 中 "eval"中捕获的变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27638601/

相关文章:

python - 有没有一种简单的方法可以为 Kivy 按钮添加边框

javascript - 在 Imacros 中的 Eval 语句内围绕双引号分割提取的文本

module - 我什么时候应该在 Racket 中使用 `protect-out`?

Python numpy 数组合并操作

python - pandas 数据框中带有附加问题的时间序列

python-2.7 - 无法在 ubuntu 16.04 中安装带有补丁 qt 的 wkhtmltopdf

javascript - Ajax:将代码注入(inject) Internet Explorer

python - 最后导入的文件会覆盖以前文件中的语句。指定导入变量的更好方法?

Python:动态存储字典的访问

python - 橙色仅随机出现在屏幕 python 的右侧