当您不确定某些东西为什么会以Python中的方式工作时,通常可以将您感到困惑的行为放在函数中,然后使用dis模块从Python字节码中反汇编它。在
让我们从代码的更简单版本开始:def foo():
exec("K = 89")
print(K)
如果您运行foo(),您将得到与更复杂函数相同的异常:
^{pr2}$
让我们把它拆开看看为什么:>>> import dis
>>> dis.dis(foo)
2 0 LOAD_GLOBAL 0 (exec)
3 LOAD_CONST 1 ('K = 89')
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
3 10 LOAD_GLOBAL 1 (print)
13 LOAD_GLOBAL 2 (K)
16 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
19 POP_TOP
20 LOAD_CONST 0 (None)
23 RETURN_VALUE
您需要注意的操作是标记为“13”的操作。这是编译器处理在函数的最后一行中查找K的地方(print(K))。它使用的是LOAD_GLOBAL操作码,这失败了,因为“K”不是全局变量名,而是我们的locals()dict中的一个值(通过exec调用添加)。在
如果我们说服编译器将K看作一个局部变量(在运行exec之前给它一个值),那么它就会知道不去寻找一个不存在的全局变量呢?在def bar():
K = None
exec("K = 89")
print(K)
如果运行此函数,则不会给您错误,但不会打印出预期值:>>> bar()
None
让我们拆开看看为什么:>>> dis.dis(bar)
2 0 LOAD_CONST 0 (None)
3 STORE_FAST 0 (K)
3 6 LOAD_GLOBAL 0 (exec)
9 LOAD_CONST 1 ('K = 89')
12 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
15 POP_TOP
4 16 LOAD_GLOBAL 1 (print)
19 LOAD_FAST 0 (K)
22 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
25 POP_TOP
26 LOAD_CONST 0 (None)
29 RETURN_VALUE
注意“3”和“19”处使用的操作码。Python编译器使用STORE_FAST和LOAD_FAST将局部变量K的值放入插槽0,然后再将其取出。使用带编号的槽明显比从locals()这样的字典中插入和获取值快得多,这就是为什么Python编译器对函数中的所有局部变量访问都这么做。你不能通过修改locals()返回的字典来覆盖槽中的局部变量(就像exec那样,如果你不给它传递一个dict来用于它的命名空间)。在
实际上,让我们尝试一下我们函数的第三个版本,当我们将K定义为常规局部变量时,我们再次查看locals:def baz():
K = None
exec("K = 89")
print(locals())
这次您也不会在输出中看到89!在>>> baz()
{"K": None}Update and return a dictionary representing the current local symbol table.
局部变量K值所在的槽没有被exec语句更改,它只修改locals()dict。当您再次调用locals()时,Python用插槽中的值“更新”字典,用exec替换其中存储的值。在
这就是为什么医生接着说:Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.
您的exec调用正在修改locals()dict,并且您会发现它的更改并不总是被以后的代码看到。在