max*_*max 14 python scope function python-3.x
a = 10
def f():
print(1)
print(a) # UnboundLocalError raised here
a = 20
f()
Run Code Online (Sandbox Code Playgroud)
这个代码当然会提升UnboundLocalError: local variable 'a' referenced before assignment
.但是为什么在这print(a)
条线上提出了这个例外?
如果解释器逐行执行代码(就像我认为的那样),它就不会知道print(a)
到达时出了什么问题; 它只会认为是a
指全局变量.
因此,解释器似乎提前读取整个函数以确定是否a
用于赋值.这记录在哪里?还有其他任何解释器向前看的场合(除了检查语法错误)吗?
为了澄清,异常本身非常清楚:全局变量可以在没有global
声明的情况下读取,但不能写入(这种设计可以防止由于无意中修改全局变量而导致的错误;这些错误特别难以调试,因为它们会导致错误发生在远离错误代码的位置).我只是好奇为什么早点提出异常.
Ant*_*lvy 14
根据Python的文档,解释器将首先注意到a
在范围内命名的变量的赋值f()
(无论函数中赋值的位置),然后作为结果仅将变量识别a
为此范围中的局部变量.此行为有效地影响全局变量a
.
然后引发异常"提前",因为执行代码"逐行"的解释器将遇到引用局部变量的print语句,此时此时尚未绑定(请记住,Python正在local
此处查找变量) ).
正如您在问题中提到的那样,必须使用global
关键字明确告诉编译器此范围中的赋值是对全局变量进行的,正确的代码将是:
a = 10
def f():
global a
print(1)
print(a) # Prints 10 as expected
a = 20
f()
Run Code Online (Sandbox Code Playgroud)
正如@ 2rs2ts在一个现在删除的答案中所说,这很容易解释为"Python不仅仅被解释,它被编译成字节码而不仅仅是逐行解释".
在Python参考手册的名称解析部分中,说明了这一点:
[..]如果当前作用域是函数作用域,并且名称引用的是尚未绑定到使用该名称的值的局部变量,
UnboundLocalError
则会引发异常[...]
这是关于何时UnboundLocalError
发生的官方消息.如果您查看CPython为您的函数生成的字节码f
,dis
您可以看到它尝试在其值尚未设置时从本地范围加载名称:
>>> dis.dis(f)
3 0 LOAD_GLOBAL 0 (print)
3 LOAD_CONST 1 (1)
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
4 10 LOAD_GLOBAL 0 (print)
13 LOAD_FAST 0 (a) # <-- this command
16 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
19 POP_TOP
5 20 LOAD_CONST 2 (20)
23 STORE_FAST 0 (a)
26 LOAD_CONST 0 (None)
29 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
如您所见,'a'
通过以下LOAD_FAST
命令将名称加载到堆栈中:
13 LOAD_FAST 0 (a)
Run Code Online (Sandbox Code Playgroud)
这是用于获取函数中的局部变量的命令(FAST
由于它比从全局范围加载要快得多LOAD_GLOBAL
).
这实际上与a
之前定义的全局名称无关.这与CPython将假设你玩得很好并生成一个LOAD_FAST
for引用的事实有关,'a'
因为'a'
它被分配给函数体内部(即做一个本地名称).
对于具有单个名称访问且没有相应赋值的函数,CPython不生成a LOAD_FAST
而是使用以下内容查看全局范围LOAD_GLOBAL
:
>>> def g():
... print(b)
>>> dis.dis(g)
2 0 LOAD_GLOBAL 0 (print)
3 LOAD_GLOBAL 1 (b)
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
因此,解释器似乎提前读取整个函数以确定是否
a
用于赋值.这记录在哪里?还有其他任何解释器向前看的场合(除了检查语法错误)吗?
在参考手册的"复合语句"部分中,对于函数定义说明如下:
函数定义是可执行语句.它的执行将当前本地命名空间中的函数名绑定到一个函数对象(该函数的可执行代码的包装器).
具体来说,它将名称绑定f
到一个保存已编译代码的函数对象f.__code__
,这dis
对我们来说是美化.
归档时间: |
|
查看次数: |
1118 次 |
最近记录: |