python-3.7 - 在python3中调用locals()时发生了什么?

标签 python-3.7

我正在使用 Python3.7.2

让我感到困惑的代码如下所示:

def foo(cond):
    if cond:
        z = 1
        return z
    else:
        loc = locals()
        x = 1
        locals()['y'] = 2
        exec("z = x + y")
        print(loc)
        print(locals())

foo(False)

这是它打印的结果:
{'cond': False, 'loc': {...}, 'x': 1, 'y': 2, 'z': 3}
{'cond': False, 'loc': {...}, 'x': 1, 'y': 2}

exec 函数更改了本地命名空间,但是当我执行 locals() 时,变量 z 消失了。

但是,如果我像这样更改代码:
def foo(cond):
    if cond:
        return 1
    else:
        loc = locals()
        x = 1
        locals()['y'] = 2
        exec("z = x + y")
        print(loc)
        print(locals())

foo(False)

结果将是:
{'cond': False, 'loc': {...}, 'x': 1, 'y': 2, 'z': 3}
{'cond': False, 'loc': {...}, 'x': 1, 'y': 2, 'z': 3}

我真的很困惑。 :(

最佳答案

简而言之: locals() 返回的字典只是真实局部变量的副本——它们存储在数组中,而不是字典中。您可以操作 locals()字典,随意添加或删除条目。 Python 不在乎,因为这只是它所使用的局部变量的副本。但是,每次拨打locals() , Python 将所有局部变量的当前值复制到字典中,替换您或 exec() 的任何其他内容。之前放进去。所以,如果 z是一个适当的局部变量(如代码的第一个版本,但不是第二个版本),locals()将恢复其当前值,该值恰好是“未设置”或“未定义”。

实际上,从概念上讲,Python 将局部变量存储在字典中,可以通过函数 locals() 检索该字典。 .但是,在内部,它确实是两个数组。本地人的名字是代码本身的一个属性,因此在代码对象中存储为 code.co_varnames .然而,它们的值作为 fast 存储在帧中。 (无法从 Python 代码访问)。您可以在 co_varnames 上找到更多信息等在 inspect dis 模块,分别。

内置函数locals()实际上调用了一个方法 fastToLocals() 在当前框架对象上。如果我们用它的 fastToLocals 来写框架Python 中的方法,它可能看起来像这样(我在这里省略了很多细节):

class frame:
    def __init__(self, code):
        self.__locals_dict = None
        self.f_code = code
        self.__fast = [0] * len(code.co_varnames)

    def fastToLocals(self):
        if self.__locals_dict is None:
            self.__locals_dict = {}
        for (key, value) in zip(self.f_code.co_varnames, self.__fast):
            if value is not null:             # Remember: it's actually C-code
                self.__locals_dict[key] = value
            else:
                del self.__locals_dict[key]   # <- Here's the magic
        return self.__locals_dict

def locals():
    frame = sys._getframe(1)
    frame.fastToLocals()

用简单的英语:拨打电话时获得的字典 locals()缓存在当前帧中。调用 locals()重复会给你相同的字典(上面代码中的 __locals_dict)。但是,每次您都拨打locals() ,框架将使用局部变量当时具有的当前值更新此字典中的条目。如前所述 here ,当没有设置局部变量时,__locals_dict中的条目被删除。这就是它的全部意义。

在代码的第一个版本中,第三行显示 z = 1 ,这使得 z一个局部变量。在 else然而,该局部变量z未设置(即会引发 UnboundLocalError ),因此从 __locals_dict 中删除.在您的代码的第二个版本中,没有分配给 z ,所以它不是局部变量和 locals()函数不关心它。

局部变量集实际上是由编译器固定的。这意味着您不能在运行时添加或删除局部变量。这是exec()的问题,因为您显然在使用 exec()这里定义一个局部变量z . Python的出路是说exec()专卖店 z作为 _locals_dict 中的局部变量字典,虽然它不能把它放到这个字典后面的数组中。

结论:局部变量的值实际上是存放在一个数组中,而不是存放在locals()返回的字典中。 .打电话时locals() ,用从字典中获取的真实当前值更新字典。 exec() ,另一方面,只能将其局部变量存储在字典中,而不能存储在数组中。如 z是一个适当的局部变量,它将被对 locals() 的调用覆盖。与其当前值(即“不存在”)。如 z不是一个适当的局部变量,它将原封不动地留在字典中。

Python 的规范规定,您在函数内部分配的任何变量都是局部变量。您可以通过使用 global 更改此默认值。或 nonlocal , 当然。但是只要你写 z = ... , z成为局部变量。另一方面,如果您只有 x = z在您的代码中,编译器假定 z而是一个全局变量。这就是为什么行 z = 1使一切变得不同:它标志着 z作为在 fast 中接收其位置的局部变量大批。

关于exec() : 一般来说,编译器不可能真正知道什么代码exec()将要执行(在您的情况下,它可以使用字符串文字,但由于这是一种罕见且无趣的情况,因此它永远不会尝试)。因此,编译器无法知道 exec() 中的代码是什么局部(或全局)变量。可能会访问,并且不能在计算局部变量数组应该有多大时包括它。

顺便说一句:局部变量在数组中而不是在适当的字典中管理是可能引发 UnboundLocalError 的原因。而不是 NameError .在局部变量的情况下,Python 解释器实际上识别名称并确切知道它的值在哪里(在上面提到的 fast 数组中)。但如果该条目是 null ,它不能返回有意义的东西,因此引发 UnboundLocalError .然而,对于全局名称,Python 会在全局变量和内置字典中真正搜索具有给定名称的变量。在这种情况下,请求名称的变量可能真的不存在。

关于python-3.7 - 在python3中调用locals()时发生了什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62124836/

相关文章:

python - 类型提示中的可选联合

python-3.7 - aiohttp - 如何在类命名空间中保存持久 session

python - Try, except 子句 with if, else

asynchronous - asyncio.create_task 立即执行协程

python - 如何修复 "AttributeError at/api/doc ' AutoSchema' 对象在 Django 中没有属性 'get_link'"错误

python - 函数在 500 次迭代后打印 500 个值函数更新值到 listwidget

python 3.7 : Initialize objects with dataclasses module?

python-3.7 - 如何修复 ' Not all arguments converted during string formatting? '

Python ctype : char array to c function is not getting updated when the c function writes values to it

python - 更新到 Django 2.2.4 和 python 3.7 (toml.py) 后无法运行 mange.py runserver