Python 2 与 3 中的评估范围

Eval scope in Python 2 vs. 3

我在 Python 3 中遇到了奇怪的 eval 行为 - 当在列表理解中调用 eval 时,局部变量没有被拾取。

def apply_op():
    x, y, z = [0.5, 0.25, 0.75]
    op = "x,y,z"
    return [eval(o) for o in op.split(",")]
print(apply_op())

错误 Python 3:

▶ python --version
Python 3.4.3
▶ python eval.py
Traceback (most recent call last):
  File "eval.py", line 7, in <module>
    print(apply_op())
  File "eval.py", line 5, in apply_op
    return [eval(o) % 1 for o in op.split(",")]
  File "eval.py", line 5, in <listcomp>
    return [eval(o) % 1 for o in op.split(",")]
  File "<string>", line 1, in <module>
NameError: name 'x' is not defined

它在 Python 2:

中工作正常
▶ python --version
Python 2.7.8
▶ python eval.py
[0.5, 0.25, 0.75]

将它移到列表理解之外可以解决问题。

def apply_op():
    x, y, z = [0.5, 0.25, 0.75]
    return [eval("x"), eval("y"), eval("z")]

这是预期的行为,还是错误?

错误跟踪器中有一个 已关闭 问题:Issue 5242

此错误的解决方案是不会修复

本期的一些评论如下:

This is expected, and won't easily fix. The reason is that list comprehensions in 3.x use a function namespace "under the hood" (in 2.x, they were implemented like a simple for loop). Because inner functions need to know what names to get from what enclosing namespace, the names referenced in eval() can't come from enclosing functions. They must either be locals or globals.

eval() is probably already an hack, there's no need to add another hack to make it work. It's better to just get rid of eval() and find a better way to do what you want to do.

如果你想:

def apply_op():
    x, y, z = [0.5, 0.25, 0.75]
    op = "x,y,z"
    return [eval(o) for o in op.split(",")]
print(apply_op())

要工作,您需要捕获局部变量和全局变量,因为问题是 eval(o)eval(o, globals(), locals()) 相同,但由于 eval 出现在生成器函数中这些函数的结果与 eval 没有包装函数时的结果不同,因此在生成器外部捕获它们并在内部使用它们:

def apply_op():
    x, y, z = [0.5, 0.25, 0.75]
    op = "x,y,z"
    _locals = locals()
    _globals = globals()
    return [eval(o, _globals, _locals) for o in op.split(",")]
print(apply_op())

或者更好,因为 x,y,z 是局部变量,字符串只是变量名:

def apply_op():
    x, y, z = [0.5, 0.25, 0.75]
    op = "x,y,z"
    _locals = locals()
    return [_locals[o] for o in op.split(",")]
print(apply_op())