转载文章
转载文章
作者:骏马金龙
出处:http://www.cnblogs.com/f-ck-need-u/p/9925021.html
Python作用域详述
作用域是指变量的生效范围,例如本地变量、全局变量描述的就是不同的生效范围。
python的变量作用域的规则非常简单,可以说是所有语言中最直观、最容易理解的作用域。
在开始介绍作用域之前,先抛一个问题:
x=1
def f():
x=3
g()
print("f:",x) # 3
def g():
print("g:",x) # 1
f()
print("main:",x) # 1
上面的代码将输出3、1、1。解释参见再述作用域规则
python作用域规则简介
它有4个层次的作用域范围:内部嵌套函数、包含内部嵌套函数的函数自身、全局作用域、内置作用域。上面4个作用域的范围排序是按照从内到外,从小到大排序的。
其中:
- 内置作用域是预先定义好的,在
__builtins__
模块中。这些名称主要是一些关键字,例如open、range、quit等
- 全局作用域是文件级别的,或者说是模块级别的,每个py文件中处于顶层的变量都是全局作用域范围内的变量
- 本地作用域是函数内部属于本函数的作用范围,因为函数可以嵌套函数,嵌套的内层函数有自身的内层范围
- 嵌套函数的本地作用域是属于内层函数的范围,不属于外层
所以对于下面这段python代码来说,如果它处于a.py文件中,且没有嵌套在其它函数内:
X=1
def out1(i):
X=2
Y='a'
print(X)
print(i)
def in1(n):
print(n)
print(X,Y)
in1(3)
out1(2)
那么:
处于全局作用域范围的变量有:X、out1
处于out1本地作用域范围的变量有:i、X、Y、in1
处于嵌套在函数out1内部的函数in1的本地作用域范围的变量有:n
注意上面的函数名out1和in1也是一种变量。
如下图所示:
搜索规则
当在某个范围引用某个变量的时候,将从它所在的层次开始搜索变量是否存在,不存在则向外层继续搜索。搜索到了,则立即停止。
例如函数ab()中嵌套了一个函数cd(),cd()中有一个语句print(x)
,它将首先检查cd()函数的本地作用域内是否有x,如果没有则继续检查外部函数ab()的本地作用域范围内是否有x,如果没有则再次向外搜索全局范围内的变量x,如果还是没有,则继续搜索内置作用域,像"x"这种变量名,在内置作用域范围内是不存在的,所以最终没有搜索到,报错。如果一开始在cd()中就已经找到了变量x,就不会再搜索ab()范围以及更外层的范围。
所以,内层范围可以引用外层范围的变量,外层范围不包括内层范围的变量。
内置作用域
内置作用域主要是一些内置的函数名、内置的异常等关键字。例如open,range,quit等。
两种方式可以搜索内置作用域:一是直接导入builtins模块,二是让python自动搜索。导入builtins模块会让内置作用域内的变量直接置于当前文件的全局范围,自动搜索内置作用域则是最后的阶段进行搜索。
一般来说无需手动导入builtins模块,不过可以看看这个模块中包含了哪些内置变量。
>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', ...............
'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
变量掩盖和修改规则
如果在函数内部引用了一个和全局变量同名的变量,且不是重新定义、重新赋值(其实python中没有变量声明的概念,只有赋值的概念),那么函数内部引用的是全局变量。
例如,下面的函数g()中,print函数中的变量x并未在g()中单独定义或赋值,所以这个x引用的是全局变量x,它将输出值3。
x=3
def g():
print(x) # 引用全局变量x
如果函数内部重新赋值了一个和全局变量名称相同的变量,则这个变量是本地变量,它会掩盖全局变量。注意是掩盖而非覆盖,掩盖的意思是出了函数的范围(函数退出),全局变量就会恢复。或者换句话说,在函数内部看到的是本地变量x=2
,在函数外部看到的是全局变量x=3
。
例如:下面的g()中重新声明了x,这个x称为g()的本地变量,全局变量x=3
暂时被掩盖(当然,这是对该函数来说的掩盖)。
x=3
def g():
x=2 # 定义并赋值本地变量x
print(x) # 引用本地变量x
python是一种解释性语言,读一行解释一行,读了下一行就忘记前一行(详细见下文)。所以在使用变量之前必须先进行变量的定义(声明)。
例如下面是错误的:
def g():
print(x)
x=3
g()
错误信息:
UnboundLocalError: local variable 'x' referenced
before assignment
这个很好理解,但是下面和同名的全局变量混合的时候,就不那么容易理解了:
x=1
def g():
print(x)
x=2
g()
这里也会报错,而不是输出x=1或2。
这里需要解释一下,虽说python是逐行解释的。但每个函数属于一个区块,这个区块范围是一次性解释的,并不会读一行忘记一行,而是一直读,读完整个区块再解释。所以,读完整个g()区块后,首先就记住了重新定义了本地变量x=2
,于是g()中所有使用变量x的时候,都是本地变量x,所以print(x)中的x也是本地变量,但这违反了使用变量前先赋值的规则,所以也会报错。
因此,在函数内修改和全局变量同名的变量前,必须先修改,再使用该变量。所以,上面的代码中,x=2
必须放在print的前面:
x=1
def g():
x=2
print(x)
g()
所以,对于函数来说,也必须先定义函数,再调用函数。下面是错误的:
g()
def g():
x=2
print(x)
报错信息:
NameError: name 'g' is not defined
但是下面的代码中,f()中先调用了g(),然后才定义g(),为什么能执行呢:
x=1
def f():
x=3
g()
print("f:",x) # 3
def g():
print("g:",x) # 1
f()
print("main:",x) # 1
实际上并非是先调用了g(),python解释到def f()区块的时候,只是声明这一个函数,并非调用这个函数,真正调用f()的时候是在def g()
区块的后面,所以实际上是先声明完f()和g()之后,