在 exec 中使用空局部变量列表理解:NameError [英] list comprehension in exec with empty locals: NameError

查看:30
本文介绍了在 exec 中使用空局部变量列表理解:NameError的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下片段:

def bar():返回 1打印([bar() for _ in range(5)])

它给出了预期的输出[1, 1, 1, 1, 1].

但是,如果我尝试在空环境中 exec 相同的代码段(localsglobals 都设置为 {}),它给出 NameError:

如果 globals() 中的 'bar' 或 locals() 中的 'bar':德尔巴# 确保我们重置设置执行("定义栏():返回 1打印([bar() for _ in range(5)])""", {}, {})NameError: 名称 'bar' 未定义

如果我像 exec(…, {})exec(…) 一样调用 exec,它会按预期执行.>

为什么?

还要考虑以下代码段:

def foo():定义栏():返回 1print('bar' in globals()) # Falseprint('bar' in locals()) # Trueprint(['bar' in locals() for _ in [1]]) # [False]print([bar() for _ in [1, 2]]) # [1, 1]

就像在我的第一个 exec 中一样,我们在列表理解中的 locals 中没有 bar.但是,如果我们尝试调用它,它会起作用!

解决方案

问题的解决方案在这里:

<块引用>

在所有情况下,如果省略可选部分,则代码在当前范围内执行.如果只提供了globals,它必须是一个字典,用于全局和局部变量.如果给出了 globals 和 locals,则它们分别用于全局变量和局部变量. 如果提供,则 locals 可以是任何映射对象.请记住,在模块级别,全局变量和局部变量是同一个字典.如果 exec 获取两个单独的对象作为全局变量和局部变量,则代码将像嵌入在类定义中一样执行.

https://docs.python.org/3/library/functions.html#exec

基本上,您的问题是 bar 是在 locals 的范围内定义的,并且仅在 locals 中定义.因此,这个 exec() 语句有效:

exec("""定义栏():返回 1打印(条())""", {}, {})

然而,列表推导式创建了一个新的局部作用域,其中 bar 未定义,因此无法查找.

这种行为可以用以下方式说明:

exec("""定义栏():返回 1打印(条())打印(当地人())打印([locals() for _ in range(1)])""", {}, {})

哪个返回

1{'bar': <0x108efde18 处的功能栏>}[{'_': 0, '.0': }]

编辑

在您的原始示例中,bar 的定义位于(模块级)全局范围内.这对应于

<块引用>

请记住,在模块级别,全局变量和本地变量是同一个字典.

exec 示例中,您通过传递两个不同的字典在全局变量和本地变量之间引入了人为的范围分割.如果您传递了相同的一个或仅传递了 globals 一个(这反过来意味着这个将用于 globalslocals),您的示例也将起作用.

至于编辑中介绍的示例,这归结为python中的范围规则.详细解释请阅读:https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces

简而言之,虽然 bar 不在列表推导式的局部范围内,也不在全局范围内,但它在 foo 的范围内.并且给定 Python 作用域规则,如果在局部作用域中找不到变量,则会在封闭作用域中搜索它,直到到达全局作用域.在您的示例中,foo 的作用域介于局部作用域和全局作用域之间,因此将在搜索结束之前找到 bar.

然而,这仍然与 exec 示例不同,在该示例中,您传入的 locals 作用域并未包含列表推导式的作用域,而是与其完全分开.

可以在此处找到对范围规则的另一个很好的解释,包括插图:http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html

Consider the following snippet:

def bar():
    return 1
print([bar() for _ in range(5)])

It gives an expected output [1, 1, 1, 1, 1].

However, if I try to exec the same snippet in empty environment (locals and globals both are set to {}), it gives NameError:

if 'bar' in globals() or 'bar' in locals():
    del bar
# make sure we reset settings

exec("""
def bar():
    return 1
print([bar() for _ in range(5)])
""", {}, {})

NameError: name 'bar' is not defined

If I invoke exec like exec(…, {}) or exec(…), it is executed as expected.

Why?

EDIT:

Consider also the following snippet:

def foo():
    def bar():
        return 1
    print('bar' in globals()) # False
    print('bar' in locals()) # True
    print(['bar' in locals() for _ in [1]]) # [False]
    print([bar() for _ in [1, 2]]) # [1, 1]

Just like in my first exec, we don't have bar in locals inside list comprehension. However, if we try to invoke it, it works!

解决方案

The solution to your problem lies here:

In all cases, if the optional parts are omitted, the code is executed in the current scope. If only globals is provided, it must be a dictionary, which will be used for both the global and the local variables. If globals and locals are given, they are used for the global and local variables, respectively. If provided, locals can be any mapping object. Remember that at module level, globals and locals are the same dictionary. If exec gets two separate objects as globals and locals, the code will be executed as if it were embedded in a class definition.

https://docs.python.org/3/library/functions.html#exec

Basically, your problem is that bar is defined in the scope of locals and only in locals. Therefore, this exec() statement works:

exec("""
def bar():
    return 1
print(bar())
""", {}, {})

The list comprehension however creates a new local scope, one in which bar is not defined and can therefore not be looked up.

This behaviour can be illustrated with:

exec("""
def bar():
    return 1
print(bar())
print(locals())
print([locals() for _ in range(1)])
""", {}, {})

which returns

1
{'bar': <function bar at 0x108efde18>}
[{'_': 0, '.0': <range_iterator object at 0x108fa8780>}]

EDIT

In your original example, the definition of bar is found in the (module level) global scope. This corresponds to

Remember that at module level, globals and locals are the same dictionary.

In the exec example, you introduce an artificial split in scopes between globals and locals by passing two different dictionaries. If you passed the same one or only the globals one (which would in turn mean that this one will be used for both globals and locals) , your example would also work.

As for the example introduced in the edit, this boils down to the scoping rules in python. For a detailed explanation, please read: https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces

In short, while bar is not in the local scope of the list comprehension and neither in the global scope, it is in the scope of foo. And given Python scoping rules, if a variable is not found in the local scope, it will be searched for in the enclosing scopes until the global scope is reached. In your example, foo's scope sits between the local scope and the global scope, so bar will be found before reaching the end of the search.

This is however still different to the exec example, where the locals scope you pass in is not enclosing the scope of the list comprehension, but completely divided from it.

Another great explanation of scoping rules including illustrations can be found here: http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html

这篇关于在 exec 中使用空局部变量列表理解:NameError的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆