Python lambda + exec范围错误

时间:2015-08-12 17:18:35

标签: python python-3.x lambda scope

在下面的代码中,对iszero的第一次引用成功,但第二次引用(在lambda中)给出了NameError: 'iszero' is not defined

myblock = """
def iszero(x):
    return x == 0

print(iszero(0)) #works

args = [0, 1, 2]

flts = list(filter(lambda f:(not iszero(f)), args)) #NameError
"""

mylocals = {}
exec(myblock, globals(), mylocals)

2 个答案:

答案 0 :(得分:2)

我将其归类为Python错误(至少是文档错误)。

您应该在Python bug tracker...

中打开一个问题

问题与lambda无关,看起来像提供locals词典时由exec创建的闭包没有正确的范围解析。

奇怪的是,PyPy具有相同的行为,因此可能这确实是预期的结果(但我无法理解它在哪里记录)。

修改

这可能看起来很奇怪,但这是预期的行为。

原因是当exec同时提供了globallocal字典时,代码就会被执行,就像它在类定义的主体中一样

该上下文非常具体,并且在其中创建的闭包不能访问在类的范围内定义的名称(这就是为什么需要在方法中使用myclass.myattribute来访问类属性 - 这是一个从全局开始的查找)。

这种奇怪的行为并不经常发生,因为通常类定义范围内的代码只对简单的赋值(对于类级属性)和定义(对于方法)不会尝试捕获这些名称。

答案 1 :(得分:0)

我在此理解的是,exec(myblock)这始终是向locals()添加实体。我确实验证了以下情况..

当您使用locals时,这就是python管理globalsexec的方式。

if (globals == Py_None) {
    globals = PyEval_GetGlobals();
    if (locals == Py_None) {
        locals = PyEval_GetLocals();
        if (locals == NULL)
            return NULL;
    }
    if (!globals || !locals) {
        PyErr_SetString(PyExc_SystemError,
                        "globals and locals cannot be NULL");
       return NULL;
    }
}
else if (locals == Py_None)
    locals = globals;

按照上面的逻辑,当你执行exec(myblock, None, None)时,python获取globals()并且与上述逻辑的exec(myblock, globals(), globals())表达式相同。以下是我们同时提供locals() is globals()时验证是否为None的简单表达式。

>>> exec("print(locals() is globals())", None, None)
True

同样地,当我们提供locals作为None时,根据上面的python逻辑if (locals == Py_None) locals = globals,两者都会引用相同的对象,即globals。< / p>

>>> exec("print(locals() is globals())", globals(), None)
True

当我们将locals作为空dict {}传递时,根据上述逻辑,它不会对局部变量进行任何类型的更改。

>>> exec("print(locals() is globals())", globals(), {})
False

现在让我们看看你的案子中发生了什么......

我已经修改了你的脚本并添加了print(list(globals()), list(locals())); print(locals() is globals())这两行,如下所示。

myblock = """
def iszero(x):
    return x == 0

args = [0, 1, 2]

# added these two stmts here
print(list(locals()))
print(list(globals()))
print(locals() is globals())

flts = list(filter(lambda f:(not iszero(f)), args)) #NameError
"""

使用exec locals作为空字典执行上述字符串后。

>>> exec(myblock, globals(), {})
['iszero', 'args']
['__package__', 'myblock', '__builtins__', '__spec__', '__doc__', '__loader__', '__name__']
False
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 12, in <module>
  File "<string>", line 12, in <lambda>
NameError: name 'iszero' is not defined

很明显,locals空间会添加所有属性。因此,如果您将iszero作为globals传递,则永远不会在locals中看到{}方法。

您收到NameError list(filter(lambda f:(not iszero(f)), args))的原因是它开始在globals而不是locals进行查找,并且您的全局变量没有定义方法。< / p>