python进阶1——闭包

什么是闭包?闭包有什么用?为什么要用闭包?今天我们就带着这3个问题来一步一步认识闭包。闭包和函数紧密联系在一起,介绍闭包前有必要先介绍一些背景知识,诸如嵌套函数、变量的作用域等概念

一切皆对象

1
2
3
4
5
def foo():
num = 10

print(type(foo))
<class 'function'>

一个函数即为一个对象,这一点与C++等其他语言还是有区别的

变量作用域

局部变量,函数外部不能访问,作用域仅限函数内部

1
2
3
4
5
def foo():
num = 10

print(num)
NameError: name 'num' is not defined

全局变量

1
2
3
4
5
6
num = 10
def foo():
print(num)

foo()
10

什么是闭包

函数身为第一类对象,它可以作为函数的返回值返回

1
2
3
4
5
6
7
8
9
def A():
a = 10
def B():
print(a)
return B

f = A() # f为对象B
f()
10

正常情况下,a的作用域仅在 A() 函数内部,当我们调用完成 A() 后,按理来说在函数外部无法再使用 a,但是我们调用 f() 时,a仍然可以使用。闭包使得局部变量在函数外被访问成为可能。

维基百科中对闭包的定义是这样的:

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

这里的 f 便是一个闭包,闭包本质上是一个函数,它由两部分组成,B函数和变量 a,二者一同存在,组成的实体便称为闭包。

闭包,顾名思义,就是一个封闭的包裹,里面包裹着自由变量,就像在类里面定义的属性值一样,自由变量的可见范围随同包裹,哪里可以访问到这个包裹,哪里就可以访问到这个自由变量。

为什么要使用闭包

闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关联起来。这一点与面向对象编程是非常类似的。

一般来说,当对象中只有一个方法时,这时使用闭包是更好的选择。

1
2
3
4
5
6
7
8
9
10
def adder(x):
def wrapper(y):
return x + y
return wrapper

adder5 = adder(5)
# 输出 15
adder5(10)
# 输出 11
adder5(6)

这个例子中,局部变量为 x 的值,为 5。这比使用类来实现更优雅,此外装饰器也是基于闭包的一类应用。

closure 属性

所有函数都有一个 __closure__属性,如果这个函数是一个闭包的话,那么它返回的是一个由 cell 对象 组成的元组对象。cell 对象的cell_contents 属性就是闭包中的自由变量。

1
2
3
4
5
6
7
8
9
10
11
def A():
a = 10
def B():
print(a)
return B

f = A()
print(A.__closure__)
# None
print(f.__closure__)
# (<cell at 0x017273B0: int object at 0x56D7D4C0>,)

单独的函数 A 并不是一个闭包,而 f 则为一个闭包

1
2
print(f.__closure__[0].cell_contents)
# 10

可以看到,所有闭包都会保存环境变量的值,这也解释了为什么局部变量脱离函数之后,还可以在函数之外被访问的原因。

判断下列函数是否为闭包

1
2
3
4
5
6
7
8
9
def A():
a = 10
def B():
pass
return B

f = A()
print(f.__closure__)
# None

因为 a 在函数 B 内部并未使用,不需要存储局部变量

1
2
3
4
5
def A():
a = 10
def B():
a = a + 10
return B

这样写会报错,因为这样 a 会被认为是 B() 内部的局部变量,并不是闭包的局部变量,而局部变量在使用前必须先进行分配

可以这样进行修改

1
2
3
4
5
6
def A():
a = 10
def B():
nonlocal a # 声明这不是一个局部变量
a = a + 10
return B

实例

问题:旅行者(tourist)可以每调用一次函数 (go) ,可以使当前位置 (pos) 加相应的步数 (step)

非闭包解决 (需要使用全局变量)

1
2
3
4
5
6
7
8
9
origin = 0
def go(step):
global origin
origin = origin + step
return origin

print(go(2))
print(go(3))
print(go(5))

闭包解决

1
2
3
4
5
6
7
8
9
10
11
12
origin = 0
def factory(pos):
def go(step):
nonlocal pos
pos = pos + step
return pos
return go

tourist = factory(origin)
print(tourist(2))
print(tourist(3))
print(tourist(5))