在python中,变量查找遵循LGB原则,即优先在局部作用域(local scope)中对变量进行查找,失败则在全局作用域(global scope)中进行查找,最后尝试再内建作用域(build-in scope)内查找,如果还是未找到的话,则抛出异常。后来由于闭包和嵌套函数的出现,作用域又增加了外部作用域,这样变量的查找作用域优先级变为:局部、外部、全局和内建。 作用域由def、class、lambda等语句产生,if、try、for等语句并不会产生新的作用域。我们看下面的一个例子:
代码如下 | 复制代码 |
def scope1_f1(): def f2(): print local_v local_v = 'local' f2() print global_v if __name__ == "__main__": |
在scope1_f1函数中我们并未对global_str进行赋值(即scope1_f1的局部作用域中并不存在变量lobal_str),但程序正常输出结果'local'和'global'。
一 局部作用域中不应对全局变量进行赋值
需要注意的是虽然我们可以在函数中对全局的变量进行访问,但一旦局部作用域中对全局变量进行了赋值操作,python解释器就不会从全局作用域中查找,而会抛出UnboundLocalError错误。该规则在由局部作用域向外部作用域查找时同样有效。
代码如下 | 复制代码 |
def scope1_f1(): print global_v global_v = 'local' if __name__ == "__main__": |
运行程序我们会得到:UnboundLocalError: local variable 'global_v' referenced before assignment
有资料将该规则描述为“局部作用域中全局变量应是只读”是不准确的。因为如果变量为list等类型,我们可以通过append这样的方法来修改全局变量,而不影响局部作用域对变量的访问。
代码如下 | 复制代码 |
def scope1_f1(): print global_v global_v.add('local') if __name__ == "__main__": |
二 局部作用域中使用 global实现对全局变量进行赋值
如果说确实需要对全局变量进行赋值的话,应在局部作用域中使用global来修饰变量。python2.6中global对变量的修饰可以在函数的任意地方进行,但如果global在赋值之后的话,解释器会提示SyntaxWarning 。
代码如下 | 复制代码 |
def scope1_f1(): global global_v print global_v global_v = 'local' if __name__ == "__main__": |
三 继承类优先使用第一个基类中的变量
在没有对变量初始化的情况下,继承类会优先使用第一个基类中的变量。下面的例子会输出1而不是2。(说明:如果继承类为初始化函数,会优先调第一个基类的初始化函数,如果前面的基类都没有的话才会调后面基类的初始化函数,初始化函数对变量的修改不在本文讨论范围),
代码如下 | 复制代码 |
class sclass1(): a = 1 def run(self): print self.a class sclass2(): a = 2 def run(self): print self.a class dclass(sclass1, sclass2): if __name__ == "__main__": |
四 全局作用域指的是本模块而不是程序
在变量查找时只会在本模块范围内进行变量的查找,即使使用from xxx import *也不会垮模块查找。在python中导入一个模块可以理解为是将另外一个模块各变量赋值给当前模块的同名变量, 对当前模块中变量的赋值不会影响到导入模块的变量。
代码如下 | 复制代码 |
main.py from module2 import * if __name__ == "__main__": module2_v = 5 module2_v1 = 3 print_modul2() modul2.py |
在python中一切都是对象和变量,因此对函数、模块等的的查找同样遵循上面的规则。
最后还存在的一个问题是:使用global可以再局部作用域中对全局变量赋值,那么如何在局部作用域中对外部变量赋值呢? 目前除了使用list这样的引用传递外我还没发现其他解决方法,留在这里大家一起思考吧。
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函数返回所有的局部变量,但是不会返回嵌套作用域中的变量,实际上没有函数会返回嵌套作用域中的变量。
全局变量(Global variable)
不论是其他普通函数中,还是Class类中,都是可以通过对应的变量名,而可以直接引用的。
局部变量(automatic variable)
对于函数内,包括类Class的函数内,普通的变量,都是自动临时变量
此处插播背景知识:
变量的优先级关系
普通局部变量和全局变量的作用域,最核心的区别在于:
内部变量的优先级大于外部变量
此处即,(函数内的)局部变量,优先级大于,(外部的)全局变量
换句话说:
A。如果函数内部,出现了和全局变量同名的变量,则说明是局部变量;
B。如果,函数内,没有同名的局部变量的情况下,对应的变量,则是全局变量。
这个变量的优先级关系,不仅针对与Python语言,几乎其他所有语言,都适用,比如C/C++/C#等等。
所以,上面示例中的__init__中的name,不是全局变量:
name = “whole global name”;
中的name而是,局部变量;
其中,此处是属于那种,Python(和,或其他特殊)语言所特有的,无需声明变量,就可以直接使用的情况。
即局部变量name,并没有声明,但是就直接拿过来使用,通过:
name = newPersionName;
在初始化,设置为对应的名字,此处即”crifan”了。
类(Class)的变量
类的变量,在类的范围内,只能通过
ClassName.PropertyName
或者说
ClassName.VariableName
去访问,当前类ClassName的变量PropertyName
例子中,就是通过
Person.name
去访问类Person中的变量name的值的。
示例(Instance)的变量
示例中的变量,理论上,是用
InstanceObject.PropertyName
去访问的。
而此处,由于Python中,默认的,约定俗成的写法,把类的函数的第一个参数的名字,定义为Instance变量,且名字叫做self,所以,就变成了:
self.PropertyName
了。
所以,上述在类的函数中,想要访问Instance的变量的话,就是通过
self.name,去访问类Person的示例self中的变量name了。
什么是全局变量和局部变量
在函数外,一段代码最始开所赋值的变量,它可以被多个函数引用,这就是全局变量;
在函数内定义的变量名,只能被函数内部引用,不能在函数外引用这个变量名,这个变量的作用域就是局部的,也叫它为局部变量;
如果函数内的变量名与函数外的变量名相同,也不会发生冲突。好比下面这种情况:
x = 100
def func():
x = 55
x = 100这个赋值语句所创建的变量X,作用域为全局变量;
x = 55这个赋值语句所创建的变量X,它的作用域则为局部变量,只能在函数func()内使用。
尽管这两个变量名是相同的,但它的作用域为它们做了区分。作用域在某种程度上也可以起到防止程序中变量名冲突的作用,但如果做为玩蛇网python初学者来说,尽量避免这种情况发生比较好。
总结
1、变量的作用域由代码被赋值的位置所决定
2、变量可以在3个不同地方,对应3种不同作用域:
(一)一个变量在函数内赋值,它的作用范围被定位在函数之内
(二)当变量是在一个嵌套的函数中赋值时,对于这个嵌套的函数来说,这个变量是非本地的
(三)变量在函数外赋值,它作用域就是当前整个文件的全局变量