python-3.x - UnboundLocalError:局部变量赋值前引用
问题描述
我已经阅读了关于这个主题的类似问题,例如从这里、这里和这里,以及其他关于范围的问题,但我仍然不明白为什么答案不能解释这些测试用例。
首先看到这个:
def take_sum(a, b, c):
return a+b+c
def main():
print("take_sum" in globals())
print(take_sum(1,2,3))
main()
回报:
True
6
那么这必须有效:
# TEST 1
def take_sum(a, b, c):
return a+b+c
def main():
if "take_sum" not in globals():
take_sum = lambda a, b, c: a+b+c
print(take_sum)
main()
错误的!
回报:UnboundLocalError: local variable 'take_sum' referenced before assignment
问题1:take_sum
如果它存在于全局范围内并且在main
评估为False时如何在赋值之前引用它?
因此,让我们在 if 语句中添加一个else
子句:
# TEST 2
def take_sum(a, b, c):
return a+b+c
def main():
if "take_sum" not in globals():
take_sum = lambda a, b, c: a+b+c
else:
global take_sum
print(take_sum)
main()
回报:SyntaxError: name 'take_sum' is assigned to before global declaration
问题 2:但是如果 TEST 1 中的错误说take_sum
被引用但未分配,这怎么可能?
随后,切换 if 语句中的子句起作用:
# TEST 3
def take_sum(a, b, c):
return a+b+c
def main():
if "take_sum" in globals():
global take_sum
elif "take_sum" not in globals():
take_sum = lambda a, b, c: a+b+c
print(take_sum)
print(take_sum(1,2,3))
main()
回报:
<function take_sum at 0x7fbf8b5bb160>
6
问题 3:为什么切换子句(与 TEST 2 相比)有效?
解决方案
函数中使用的名称是全局变量还是局部变量是在编译时确定的,而不是在运行时确定的。导致异常的函数正试图以两种方式使用它,要么访问全局变量,要么提供自己的局部变量替换,但 Python 的作用域规则不允许这样做。它需要是本地的或全局的,并且不能处于模糊的非此即彼的状态。
在您的测试 1 中,该函数引发异常,因为编译器看到代码可以分配take_sum
为局部变量,因此它使take_sum
代码中的所有引用都是局部的。take_sum
一旦做出决定,您就不能再以正常方式查找全局变量。
语句实际上是一个global
编译器指令,用于改变赋值使变量成为局部变量的假设。随后的分配将在全球范围内进行,而不是在本地进行。这不是在运行时执行的东西,这就是为什么您的其他两个测试用例让您如此困惑的原因。
测试 2 失败是因为在编译器已经看到一些代码对该名称进行了本地分配之后,它试图告诉编译器这take_sum
是一个全局变量。在测试 3 中,语句首先出现,因此它使赋值(在另一个分支中!)赋值给一个全局变量。语句与赋值位于不同的分支实际上并不重要,编译器在编译时解释语句,而不是在运行时处理s 和s 的条件逻辑时。global
global
global
if
elif
它可能有助于您了解如何反汇编main
您使用dis.dis
标准库中的函数编写的一些函数。您会看到有两组不同的字节码用于加载和存储变量,LOAD_GLOBAL
/STORE_GLOBAL
用于全局变量(在所有函数中用于获取类似print
and的名称globals
)和LOAD_FAST
/STORE_FAST
用于局部变量(例如a
,b
和)。我上面谈到的编译器行为归结为它为每次查找或分配选择的字节码。c
take_sum
如果我将main
Test 1 中的函数重命名为test1
,这就是我反汇编它时得到的结果:
dis.dis(test1)
2 0 LOAD_CONST 1 ('take_sum')
2 LOAD_GLOBAL 0 (globals)
4 CALL_FUNCTION 0
6 CONTAINS_OP 1
8 POP_JUMP_IF_FALSE 18
3 10 LOAD_CONST 2 (<code object <lambda> at 0x0000019022A05F50, file "<ipython-input-23-0cc3c65f7038>", line 3>)
12 LOAD_CONST 3 ('test1.<locals>.<lambda>')
14 MAKE_FUNCTION 0
16 STORE_FAST 0 (take_sum)
5 >> 18 LOAD_GLOBAL 1 (print)
20 LOAD_FAST 0 (take_sum)
22 CALL_FUNCTION 1
24 POP_TOP
26 LOAD_CONST 0 (None)
28 RETURN_VALUE
Disassembly of <code object <lambda> at 0x0000019022A05F50, file "<ipython-input-23-0cc3c65f7038>", line 3>:
3 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 LOAD_FAST 2 (c)
8 BINARY_ADD
10 RETURN_VALUE
请注意,第take_sum
5 行的查找位于字节码中的字节 20 上,它使用LOAD_FAST
. 这是导致 的字节码UnboundLocalError
,因为如果全局函数存在,则没有本地分配。
现在,让我们看一下测试 3:
dis.dis(test3)
2 0 LOAD_CONST 1 ('take_sum')
2 LOAD_GLOBAL 0 (globals)
4 CALL_FUNCTION 0
6 CONTAINS_OP 0
8 POP_JUMP_IF_FALSE 12
3 10 JUMP_FORWARD 18 (to 30)
5 >> 12 LOAD_CONST 1 ('take_sum')
14 LOAD_GLOBAL 0 (globals)
16 CALL_FUNCTION 0
18 CONTAINS_OP 1
20 POP_JUMP_IF_FALSE 30
6 22 LOAD_CONST 2 (<code object <lambda> at 0x0000019022A43500, file "<ipython-input-26-887b66de7e64>", line 6>)
24 LOAD_CONST 3 ('test3.<locals>.<lambda>')
26 MAKE_FUNCTION 0
28 STORE_GLOBAL 1 (take_sum)
8 >> 30 LOAD_GLOBAL 2 (print)
32 LOAD_GLOBAL 1 (take_sum)
34 CALL_FUNCTION 1
36 POP_TOP
9 38 LOAD_GLOBAL 2 (print)
40 LOAD_GLOBAL 1 (take_sum)
42 LOAD_CONST 4 (1)
44 LOAD_CONST 5 (2)
46 LOAD_CONST 6 (3)
48 CALL_FUNCTION 3
50 CALL_FUNCTION 1
52 POP_TOP
54 LOAD_CONST 0 (None)
56 RETURN_VALUE
Disassembly of <code object <lambda> at 0x0000019022A43500, file "<ipython-input-26-887b66de7e64>", line 6>:
6 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 LOAD_FAST 2 (c)
8 BINARY_ADD
10 RETURN_VALUE
这次查找take_sum
发生在字节码 40 上,它是 a LOAD_GLOBAL
(成功,因为有一个该名称的全局变量)。
推荐阅读
- twig - 在 Twig 模板中设置文本区域值
- python - Python 多重继承:类构造和 __init__() 方法的参数数量未对齐并导致 TypeErrors
- android - 无法启动意图
- vb6 - 使用变量访问 VB6 控件
- database - Wordpress:将分类的 post_status 更改为草稿
- linux - 在环回中通过原始套接字发送 UDP 数据包
- java - 从 Firefox 复制并在 Ubuntu 中使用 Java 读取时,剪贴板内容混乱
- ios - iOS react-native-code-push 应用程序在调试/发布模式下工作,但带有存档的 TestFlight 应用程序的白屏
- google-chrome - 开发模式下的常量 chrome 扩展 ID
- c++ - 使用正确的编码创建新的注册表项