Python的作用域
Python 是静态作用域语言,尽管它自身是一个动态语言。也就是说,在 Python 中变量的作用域是由它在源代码中的位置决定的,这与 C 有些相似,但是 Python 与 C 在作用域方面的差异还是非常明显的。
接下来会谈论 Python 的作用域规则,在这中间也会说明一下 Python 与 C 在作用域方面的不同。
在 Python 2.0 及之前的版本中,Python 只支持 3 种作用域,即局部作用域,全局作用域,内置作用域;在 Python 2.2 中,Python 正式引入了一种新的作用域 --- 嵌套作用域;在 Python 2.1 中,嵌套作用域可以作为一个选项被开启;嵌套作用域的引入,本质上为 Python 实现了对闭包的支持,关于闭包的知识,网上有很多解释,这里就不详细展开了。相应地,变量查找顺序由之前的 LGB 变成 LEGB(L:Local,E:Enclosing,G:Global,B:Built-in)。
在 Python 中,并不是任何代码块都能引入新的作用域,这与 C 有很大的不同:
#include<stdio.h> int main() { if(2 > 0) { int i = 0; } printf("i = %d", i); return 0; }
在这段代码中,if 子句引入了一个局部作用域,变量 i 就存在于这个局部作用域中,但对外不可见,因此,接下来在 printf 函数中对变量 i 的引用会引发编译错误。
但是,在 Python 中却并非如此:
if True: i = 0 print i
在这段代码中,if 子句并没有引入一个局部作用域,变量 i 仍然处在全局作用域中,因此,变量 i 对于接下来的 print 语句是可见的。
实际上,在 Python 中,只有模块,类以及函数才会引入新的作用域,其它的代码块是不会引入新的作用域的。
在 Python 中,使用一个变量之前不必预先声明它,但是在真正使用它之前,它必须已经绑定到某个对象;而名字绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量,不论这个名字绑定发生在当前作用域中的哪个位置。
def f(): print i f()
运行结果将显示:NameError: global name 'i' is not defined。Python 首先在函数 f 的本地作用域中查找变量 i,查找失败,接着在全局作用域和内置作用域中查找变量 i,仍然失败,最终抛出 NameError 异常。
i = 0 def f(): i = 8 print i f() print i
运行结果显示:8 和 0。i = 8 是一个名字绑定操作,它在函数 f 的局部作用域中引入了新的变量 i,屏蔽了全局变量 i,因此 f 内部的 print 语句看到的是局部变量 i,f 外部的 print 语句看到的是全局变量 i。
i = 0 def f(): print i i = 0 f()
运行结果显示:UnboundLocalError: local variable 'i' referenced before assignment。在这个例子当中,函数 f 中的变量 i 是局部变量,但是在 print 语句使用它的时候,它还未被绑定到任何对象之上,所以抛出异常。
print i i = 0
不论是以交互的方式运行,还是以脚本文件的方式运行,结果都显示:NameError: name 'i' is not defined。这里的输出结果又与上一个例子不同,这是因为它在顶级作用域(模块作用域)的缘故。对于模块代码而言,代码在执行之前,没有经过什么预处理,但是对于函数体而言,代码在运行之前已经经过了一个预处理,因此不论名字绑定发生在作用域的那个位置,它都能感知出来。Python 虽然是一个静态作用域语言,但是名字查找确实动态发生的,因此直到运行的时候,才会发现名字方面的问题。
在 Python 中,名字绑定在所属作用域中引入新的变量,同时绑定到一个对象。名字绑定发生在以下几种情况之下:
1. 参数声明:参数声明在函数的局部作用域中引入新的变量;
2. 赋值操作:对一个变量进行初次赋值会在当前作用域中引入新的变量,后续赋值操作则会重新绑定该变量;
3. 类和函数定义:类和函数定义将类名和函数名作为变量引入当前作用域,类体和函数体将形成另外一个作用域;
4.import 语句:import 语句在当前作用域中引入新的变量,一般是在全局作用域;
5.for 语句:for 语句在当前作用域中引入新的变量(循环变量);
6.except 语句:except 语句在当前作用域中引入新的变量(异常对象)。
在 Python 中,类定义所引入的作用域对于成员函数是不可见的,这与 C++ 或者 Java 是很不同的,因此在 Python 中,成员函数想要引用类体定义的变量,必须通过 self 或者类名来引用它。
嵌套作用域的加入,会导致一些代码编译不过或者得到不同的运行结果,在这里 Python 解释器会帮助你识别这些可能引起问题的地方,给出警告。
locals 函数返回所有的局部变量,但是不会返回嵌套作用域中的变量,实际上没有函数会返回嵌套作用域中的变量。
参考:
http://www.python.org/dev/peps/pep-0227/
http://beastie.cs.ua.edu/cs150/book/book_13.html
http://www-inst.eecs.berkeley.edu/~selfpace/cs9honline/Q2/scope.html
http://www.saltycrane.com/blog/2008/01/python-variable-scope-notes/