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_FASTfor引用的事实有关,'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对我们来说是美化.