2016-11-26 54 views
14
a = 10 
def f(): 
    print(1) 
    print(a) # UnboundLocalError raised here 
    a = 20 
f() 

这段代码当然会产生UnboundLocalError: local variable 'a' referenced before assignment。但为什么在print(a)产品线上提出这个例外?为什么分配给全局变量的错误提前引发异常?

如果解释器一行一行地执行代码(就像我认为的那样),当达到print(a)时它不知道什么是错的;它会认为a指的是全局变量。

因此,解释器提前读取整个函数来确定是否使用a进行赋值。这是记录在任何地方?除了检查语法错误外,是否还有其他任何解释器向前看的场合?要明确,异常本身是非常清楚的:全局变量可以在没有global声明的情况下读取,但不能写入(这种设计可以防止由于无意修改全局变量而导致的错误;这些错误尤其难以调试,因为它们会导致错误远离错误代码的位置)。我只是好奇为什么这个例外很早就提出来了。

+1

我想你会发现默认情况下python无法从函数内部访问全局变量。你将不得不明确声明你的意思是使用全局。 (btw不要使用全局变量)。 – quamrana

+6

@quamrana不真实。如果您将作业移除到本地'a',代码将打印'10'。 – 2rs2ts

+0

对于它的价值,这不是特定于python 3,只是在python 2中测试,并发生相同的事情。 – 2rs2ts

回答

14

Python's documentation,解释器首先会注意到的在f()(无论在功能分配的位置)的范围名为a变量赋值,然后作为结果只能识别变量a为本地在此范围内变量。此行为有效shadows全局变量a

这个异常然后会在“提前”引发,因为执行代码“逐行”的解释器会遇到引用局部变量的print语句,这个变量在这一点上还没有绑定(请记住,Python正在寻找在这里变量为local)。

正如你在你的问题中提到,一个必须使用global关键字明确地告诉大家,在此范围内的分配做了全局变量,编译器正确的代码是:

a = 10 
def f(): 
    global a 
    print(1) 
    print(a) # Prints 10 as expected 
    a = 20 
f() 

由于@2rs2ts在一个现在被删除的答案中说,这很容易解释为“Python不仅仅被解释,它被编译成字节码,而不仅仅是逐行解释”。

+0

脚本编译为字节码而不是逐行解释的事实是否有其他(稍微)意外的后果? – max

+0

不是我意识到我的头顶,但我可能是错的。我知道的一点是,Python并没有被编译器优化太多,因为语言是非常动态的(没有静态类型,方法可以在运行中被替换等),所以它非常困难。一般而言,代码仅编译为字节码,以便在每次函数调用时删除解析时间(运行时只是在大查找表中查找字节码,而不是再次解析所有内容)。 cc @max –

+1

@max:汇编和解释与你的问题完全和完全无关。您在询问*语义*,即Python语言规范。 Python语言规范说,除非声明为'global',否则在函数中分配的所有变量都是本地的。期。编译和解释是* Pragmatics *的问题,即任何特定的Python实现。但是任何特定的Python实现,无论它是编译还是解释,都必须遵守Python语言规范。否则它不会*是一个“Python实现”,它... –

8

在Python参考手册此陈述的Resolution of Names部分:

[..]如果当前范围是一个函数范围和名称是指一个局部变量还没有被势必会在其中所使用的名称的点值,一个UnboundLocalError引发异常[..]

这时候UnboundLocalError发生在官方消息。如果您在CPython的字节码与dis生成的功能f看看你可以看到它试图从局部范围加载名称时,其价值甚至还没有尚未设置:

>>> 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 

,你可以看到名字'a'LOAD_FAST命令的方式加载到堆栈:

   13 LOAD_FAST    0 (a) 

这是用来抢在功能本地变量的命令(名为FAST,由于它是相当快比从全球范围加载LOAD_GLOBAL)。

这与以前定义的全局名称a无关。这与CPython会假设你打得很好并且生成LOAD_FAST以参考'a'这一事实有关,因为'a'正在中被分配给(即作为本地名称)在函数体内部。

对于一个名称进入,并没有相应的分配功能,CPython中不产生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 

所以它出现在解释提前读取整个函数以确定是否使用a进行分配。这是记录在任何地方?除了检查语法错误外,是否还有其他任何解释器向前看的场合?

在下文中,参考手册的复合语句部分陈述对于函数定义:

函数定义是一个可执行语句。它的执行将当前局部命名空间中的函数名称绑定到函数对象(函数的可执行代码的包装器)。

具体而言,其所结合的名称f到包含经编译的代码,f.__code__,即dis prettifies我们的一个功能对象。

相关问题