在进入正题之前先简单说明一下:局部变量:函数或者方法里面定义的变量;
全局变量:本文指module级别的变量(一个python文件就是一个module)。
global
先看个例子:a = 1
def func1():
print("func1:", a)
def func2():
a = 10
print("func2:", a)
def func3():
print("func3:", a)
a = 100
print("func3:", a)
if __name__ == "__main__":
print(a)
func1()
func2()
print(a)
func3()
输出结果如下:1
func1: 1
func2: 10
1
Traceback (most recent call last):
File "/Users/allan/Desktop/test/test/test.py", line 24, in
func3()
File "/Users/allan/Desktop/test/test/test.py", line 11, in func3
print("func3:", a)
UnboundLocalError: local variable 'a' referenced before assignment
func1和func2的输出应该没有什么疑问,就是局部变量和全局变量作用域的问题:模块内都可以访问到全局变量;函数内局部变量与全局变量重名,会将全局变量隐藏掉(这种最好的理解方式就是把局部变量换个名字)。这里不再赘述。func3中,因为定义了和全局变量同名的a,所以,全局变量在整个方法中都被隐藏掉了,这个与局部变量a定义的位置没有关系。所以,在局部变量a定义之前使用a就会报异常。
这里需要说明的一个关键点是:函数或方法中的任何地方,当给某个变量赋值的时候(特别要注意这个场景限定,后面会有一个例子),Python会将该变量当成一个局部变量,除非显式的使用global关键字声明该变量为全局变量。当然,赋值是写的过程,如果是读的话,会先查有没有这个名字的局部变量,如果没有,再查有没有这个名字的全局变量。
所以说,一般global的使用场景就是当我们想在函数里面修改全局变量的值。看下面代码:a = 1
def func4():
global a
a = 100
print("func4:", a)
if __name__ == "__main__":
print(a)
func4()
print(a)
输出如下:1
func4: 100
100
借助于global关键字,我们成功的在函数func4里面修改了全局变量a的值。func4和之前的func2的唯一区别就在于没有使用global关键字。
所以,这样来看,其实global的使用还是挺简单的。下面看另外一个和global作用非常像的关键字nonlocal。
nonlocal
nonlocal关键字是Python3引入的(见PEP 3104 - Access to Names in Outer Scopes, The specification for the nonlocal statement.),PEP里面对该关键字的作用已经有了大概的说明了:访问作用域之外的(对象)名字。
还是先看个例子:def outside():
msg = 'outside'
def inside():
msg = 'inside'
print(msg)
inside()
print(msg)
if __name__ == "__main__":
outside()
这个例子和之前的例子实质一样,inside里面的msg变量其实是inside函数创建的一个局部变量而已,只不过和外层的msg同名了而已,但实质还是两个完全不同的变量。所以输出也并不意外:inside
outside
现在问题来了,如果我想在inside里面给外层的msg重新赋值(即修改外层的msg)怎么办?使用刚才介绍的global关键字行不行?试一下便知:def outside():
msg = 'outside'
def inside():
global msg
msg = 'inside'
print(msg)
inside()
print(msg)
if __name__ == "__main__":
outside()
输出结果:inside
outside
Oh, holy shit,没有成功。其实也正常,因为外层的msg并不是一个全局变量,它的作用域并不是module级别的,而只是outside函数范围内的而已。现在能实现我们上面那个需求的方式就是用nonlocal代替上面代码中的global:def outside():
msg = 'outside'
def inside():
nonlocal msg
msg = 'inside'
print(msg)
inside()
print(msg)
if __name__ == "__main__":
outside()
输出如下:inside
inside
所以,nonlocal这个关键字主要就是在闭包函数(enclosing function or closures)中使用,可以修改外层函数里面的对象。
综合来看nonlocal和global的确是非常像的,不同之处在于前者用于修改函数作用域内的局部变量,而后者用于修改module级别的全局变量。
提提神
文章最后,再来一个提神的,看下面代码输出什么:def outside():
msg = {"outside": 1}
def inside():
msg['inside'] = 2
print(msg)
inside()
print(msg)
if __name__ == "__main__":
outside()
输出结果如下:{'outside': 1, 'inside': 2}
{'outside': 1, 'inside': 2}
Oh, holy shit,有没有意外?这里没有使用nonlocal修改msg,但里面的inside函数还是修改了外边的msg。不是说不行吗?
nonlocal:这个锅我不背。
其实这个的确和nonlocal没有什么关系,文章前面已经提醒过了,只有在给变量赋值这个场景下,且没有使用global和nonlocal,Python才去创建本函数的局部变量,而不是访问全局或外层函数的变量。而上面的msg['inside'] = 2其实不是个变量赋值语句。
What?没错,字典的插入的确不是变量赋值,而是方法调用(调用的是__setitem__方法)。所以,msg['inside'] = 2的底层其实是执行了如下语句:msg.__setitem__("inside", 2)
有没有很提神?(〃'▽'〃)
References: