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
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函数返回所有的局部变量,但是不会返回嵌套作用域中的变量,实际上没有函数会返回嵌套作用域中的变量。