入职前一周都在听讲座,感受公司文化。这周开始吃硬菜,老师提到了Python的一个问题,下面简称为UnboundLocalError。老师的解释是在函数代码块里因为写操作使得扰乱了引用变量的正常顺序,导致抛出该异常,这个解释听起来有点拗口,我们直接看代码说话:

1
2
3
4
5
x = 10
def foo():
x += 1
print x
foo()

在我们的正常思路下,foo函数里面用到了x变量,因此它会在函数体内寻找该变量的声明,如果没有找到会往包含该函数体的上层空间中寻找该变量声明,以此类推直到global空间为止,按道理来说x在global里已经声明过不应该出错才是。问题的缘由,我们翻译一下老师的解释,因为x += 1这个写操作,使得原本一层一层往上找x这个变量声明的顺序被打乱了,所以就错了。因为上课时间的原因老师没有讲很细,对下面两个问题我有点不懂:

  • 1.为什么写操作会有这样的问题?
  • 2.打乱顺序具体是啥意思?

倒腾完之后,我觉得这样解释应该更好。首先我们要明白Python在赋值(即写操作)时具体做了些啥,下面引用Python Doc里的一段话,大意是说如果没有global或nonlocal来修饰一个变量的话,如果发生对这个变量的赋值操作(赋给target),那么会把这个target绑定到当前的local代码块中,而我们知道寻找变量声明时优先级最高的就是local变量,这也就是老师所说的“打乱顺序”的意思。

If the target is an identifier (name):

If the name does not occur in a global or nonlocal statement in the
current code block: the name is bound to the object in the current
local namespace.

Otherwise: the name is bound to the object in the global namespace
or the outer namespace determined by nonlocal, respectively.

但是还有一个问题,看下面这个代码:

1
2
3
4
5
6
7
8
import math
def foo():
x = math.pi
import math
print math.sin(x)
foo()

首先我们得明白import为什么会引起UnboundLocalError,它把一个对象赋值给math这个名字所以引发上面的那个问题,但是如果我们执行这个代码就会发现它居然报错在第4行,即x = math.pi,但是我们明明是在第5行才执行的import语句呀,难道还会影响之前的代码不成?这个时候我们得理解一下Python代码执行的一个过程。Python会把脚本文件先编译成btyecode然后再执行,再第一步的编译过程中会生成一个symbol table用来表示各个变量的属性,比如local、global之类的。回到上面的代码中,其实在被编译成bytecode的时候函数里面的math就已经被标记为local,而真到了runtime的时候执行到x = math.pi时才会发现,原来这个local变量根本不存在。

貌似没有什么问题…其实不对,如果import math真的是把一个对象赋值给local变量,那么也不会引起错误,因为函数就用这个新对象的数据嘛!

还得解释一下import的流程,import的时候首先会检查sys.modules里面是不是已经有了math,如果没有就会去加载相应的对象,并把这个对象赋值给math这个名字;如果sys.modules里已经存在math了,那么就不加载对象了,但是会对math又一次赋值,这就是那个罪魁祸首的赋值操作…

最后唠叨一句,过去半年眼睛做了一次手术,荒废好几个月,然后毕业了,最近刚刚工作,愿一切好起来~