8.2.3.3 global 表达式
初学Python 时,由于大家不清楚Python 的作用域规则,所以会对一些出错的情况百思不得其解。比如下面的例子:
a = 1 |
运行的结果会抛出异常,显示代码清单8-2 的[1]处有错,异常的信息为“localvariable 'a' referenced before assignment”。什么?a 没有赋值?不对呀,在module定义的作用域内明明已经建立了“a = 1”这个约束,按照LEGB 规则,这个输出结果应该是1 才对,而且在调用f 之前我们调用了函数g,g 可是已经老老实实地输出了结果,怎么到了函数f 里,同样的代码,就说没有赋值了呢?
理解这个错误的关键在于深刻理解最内嵌套作用域规则,这个规则的第一句话就是这个奇怪问题的症结所在。“由一个赋值语句引进的名字在这个赋值语句所在的作用域里是可见(起作用)的”,这句话的意思对应到这里,就是说虽然“a = 2”这个约束是在“printa”之后建立的,但是由于它们在同一个作用域内,所以在代码清单8-2 的[1]处,“a = 2”这个约束的名字a 就是可见的,按照LEGB 规则,在local 名字空间中就能找到名字a,所以使用的是local 名字空间中的a 所对应的对象。但是很不幸的是,虽然名字a 在[1]处已经可见了,但是要到[2]处对这个名字的赋值动作才会发生,a 才会引用一个有效的对象,所以在[1]处当然应该抛出一个“referenced before assignment”的异常。
更为有趣的东西隐藏在编译之后的字节码中,我们可以看看上面的代码反汇编的结果,如下所示:
a = 1 |
对于相同的“print a”,Python 竟然编译出了不同的字节码指令,在函数g 中,名字引用对应的字节码指令是LOAD_GLOBAL,意思是要在global 名字空间中查找名字;而在函数f 中,名字引用对应的字节码指令为LOAD_FAST,这条指令是指在local 名字空间中查找名字,也就是说Python 在编译时就已经知道名字究竟藏身于何处。这正说明了Python 采用的是静态作用域规则,仅仅根据程序正文就能确定名字引用策略。同时,这个现象又一次地说明了最内嵌套作用域规则是指导Python 实现(这一次是编译器的实现)的“道”。
上面的例子表明,一旦作用域中有了对于某个名字的赋值操作,这个名字就会在作用域中可见,就会出现在local 名字空间中。换句话说,就遮蔽了外围作用域的相同的名字。但是有的时候,我们就是想在函数f 中输出外围作用域的名字a,同时还要对a 进行赋值,但是这个赋值操作在我们的设想中应该改变外围作用域中的名字a 对应的对象,Python 精心地为我们准备了global 关键字。当一个作用域中出现了global 语句时,就意味着我们强制命令Python 对某个名字的引用只参考global 名字空间,而不用再去管LEGB 规则。看了下面两个例子,你就会对glboal 语句了如指掌了:
a = 1 |
a = 1 |
| 回书目 上一节 下一节 |