当您不确定某个东西为什么能在Python中正常工作时,它通常可以帮助将您困惑的行为放入函数中,然后使用该
dis模块将其从Python字节码中分解出来。
让我们从一个简单的代码版本开始:
def foo(): exec("K = 89") print(K)如果运行
foo(),您将得到与更复杂的函数相同的异常:
>>> foo()Traceback (most recent call last): File "<pyshell#167>", line 1, in <module> foo() File "<pyshell#166>", line 3, in foo print(K)NameError: name 'K' is not defined
让我们分解一下,看看为什么:
>>> import dis>>> dis.dis(foo) 20 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()字典中的值(由
exec调用添加)。
如果我们说服编译器将其
K视为局部变量(在运行之前为其提供值
exec),那么它将知道不寻找不存在的全局变量怎么办?
def bar(): K = None exec("K = 89") print(K)如果运行该函数,将不会出现错误,但不会输出期望值:
>>> bar()None
让我们分解一下原因:
>>> dis.dis(bar) 20 LOAD_ConST 0 (None) 3 STORE_FAST 0 (K) 36 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如果您不将其传递给它的命名空间的字典,也一样)。
确实,让我们尝试函数的第三个版本,
locals当我们将
K其定义为常规局部变量时,可以再次浏览:
def baz(): K = None exec("K = 89") print(locals())89这次您也不会在输出中看到!
>>> baz(){"K": None}函数文档中解释了您看到旧
K值的原因:
locals()
更新并返回代表当前本地符号表的字典。
该语句
K未更改存储本地变量值的插槽,该
exec语句仅修改
locals()字典。
locals()再次调用时,Python使用插槽中的值“更新”字典,用替换存储在其中的值
exec。
这就是为什么文档继续说:
注意: 此字典的内容不得修改;更改可能不会影响解释器使用的局部变量和自由变量的值。
您的
exec呼叫正在修改
locals()字典,并且您发现以后的代码并不总是看到其更改。



