我正在使用 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/