目录
变量作用域
global
闭包与nonlocal
1. 变量作用域
作为一门动态语言,Python变量在使用的时候,不用申明变量类型,而是直接使用的。
>>> a = 123
>>> print(a)
123
>>> a = 'hello, Python'
>>> print(a)
hello, Python
>>>
当我们在命令行敲入python3的时候,当期模块默认为__main__。
>>> print(__name__)
__main__
>>>
接下来,定义一个函数,对于函数而言,前面的变量a是外层变量,可以理解为默认模块__main__的全局变量。
在函数foo中,直接访问变量a,最终在外层找到了a。如果在函数中给变量a赋值,会覆盖全局变量。
注意,这里相当于定义了一个局部变量a,而不是改写全局变量a。
>>> def foo():
... print(a)
...
>>> foo()
hello, Python
>>>
>>> def foo():
... a = 'in foo'
... print(a)
...
>>> foo()
in foo
>>>
>>> print(a)
hello, Python
>>>
但是下面的方式会报错,因为Python在编译函数bar的时候,会扫描整个函数体,生成字节码,然后执行。在生成字节码的时候,发现a=这样的赋值语句,会认为定义了一个变量a,然后执行print(a)的时候,这时候a = 'in bar‘没有执行,故变量a没有任何绑定值,从而报错。
>>> def bar():
... print(a)
... a = 'in bar'
...
>>> bar()
Traceback (most recent call last):
File "", line 1, in
File "", line 2, in bar
UnboundLocalError: local variable 'a' referenced before assignment
>>>
2. global
上面函数bar中在执行第一条语句的时候,为什么不去访问全局变量a呢?
这是Python语法规定的,当在函数体中有赋值语句时,编译的时候就认为定义了局部变量,从而保证函数封装性。
如果非得要限定为全局变量,可以使用global关键字。但这种代码要小心,因为很容易就改变了全局变量。
如下代码,最后全局变量a变成了'in bar'。
>>> def bar():
... global a
... print(a)
... a = 'in bar'
... print(a)
...
>>> bar()
hello, Python
in bar
>>> print(a)
in bar
>>>
3. 闭包与nonlocal
在这里,不得不提下闭包这个概念。在看过很多概念性定义之后,最后在《你不知道的JavaScript》这本书中,终于有了实质性的收获:
当函数可以记住并访问所在的词法作用域时, 就产生了闭包, 即使函数是在当前词法作用域之外执行。
你没有看错,我引用了一本JavaScript书中的定义。抽象性的东西,很多都能通用。
如下代码,在内层函数add中,访问了外层函数中变量count。
每次调用fn都返回一个函数对象,该对象可以在当前全局模块环境中执行,也就是在fn函数词法作用域之外执行。
>>> def fn():
... count = 0
... def add(dt = 0):
... r = count + dt
... print(r)
... return add
...
>>> fn()
.add at 0x7efcd433e048>
>>> add = fn()
>>> add()
0
>>> add(1)
1
>>> add(123)
123
>>>
>>> some = fn()
>>> some()
0
>>> some(123)
123
>>>
但是,当我们想要在内层函数直接赋值给外层函数中的变量时,问题就来了。下面的代码,遇到了上面一样的错误。语句count += dt给count赋值了,Python认为定义了一个局部变量count,+=运算要先取到count的值,但此时内层函数中count没有绑定值,于是报错了。
>>> def fn():
... count = 0
... def add(dt = 0):
... count += dt
... print(count)
... return add
...
>>> some = fn()
>>> some()
Traceback (most recent call last):
File "", line 1, in
File "", line 4, in add
UnboundLocalError: local variable 'count' referenced before assignment
>>>
可以使用nonlocal来解决这个问题。
注意如下每次函数对象调用的结果,每次调用fn返回的函数对象,该对象持有一份count变量副本,每次调用都针对当前函数对象。
>>> def fn():
... count = 0
... def add(dt = 0):
... nonlocal count
... count += dt
... print(count)
... return add
...
>>> some = fn()
>>> some()
0
>>> some(1)
1
>>> some(123)
124
>>> some(1)
125
>>>
>>> any = fn()
>>> any()
0
>>> any(1)
1
>>> any(1)
2
>>>