python:闭包

简单说,闭包就是根据不同的配置信息得到不同的结果

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

举个例子

个人认为闭包是和高阶函数密不可分的一个概念, 先看下面一个例子

1
2
3
4
5
6
7
8
9
10
11
def make_adder(addend):
def adder(augend):
return augend + addend
return adder
p = make_adder(23)
q = make_adder(44)
print p(100)
print q(100)

我们发现,make_adder是一个函数,包括一个参数addend,比较特殊的地方是这个函数里面又定义了一个新函数,这个新函数里面的一个变量正好是外部make_adder的参数.也就是说,外部传递过来的addend参数已经和adder函数绑定到一起了,形成了一个新函数,我们可以把addend看做新函数的一个配置信息,配置信息不同,函数的功能就不一样了,也就是能得到定制之后的函数.

再看看运行结果,我们发现,虽然p和q都是make_adder生成的,但是因为配置参数不同,后面再执行相同参数的函数后得到了不同的结果.这就是闭包.

进阶

1
2
3
4
5
6
7
8
9
10
11
def hellocounter (name):
count=[0]
def counter():
count[0]+=1
print 'Hello,',name,',',str(count[0])+' access!'
return counter
hello = hellocounter('ysisl')
hello()
hello()
hello()

执行结果

1
2
3
Hello, ysisl , 1 access!
Hello, ysisl , 2 access!
Hello, ysisl , 3 access!

可以看出来 count这个变量不会随着hello = hellocounter('ysisl')运行结束而被回收, 而是会一直在hello函数的生命周期存在. 而hello不仅能访问函数内部的变量, 也能访问外部的变量count, 但是count并不是全局变量, 它只和hello绑定

至于为什么count不直接用一个int而是列表, 据说是因为python2的一个Bug

闭包应用之装饰器

可查看我的另外一篇博客装饰器

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def memoize(f):
memo = {}
def helper(x):
if x not in memo:
memo[x] = f(x)
return memo[x]
return helper
@memoize
def fib(n):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
#fib = memoize(fib)
print(fib(40))

memoize是一个装饰器, 被memoize装饰的函数fib和它变量memo组成一个闭包, 这个函数fib可以访问memo


附: 数学上的闭包

  • 集合A的闭包定义为所有包含A的闭集之交。A的闭包是包含A的最小闭集。
  • 离散数学中,一个关系R的闭包,是指加上最小数目的有序偶而形成的具有自反性,对称性或传递性的新的有序偶集,此集就是关系R的闭包。

我个人认为可以把函数看成是集合A, 而把函数的上下文(比如变量)看成是A的边界. 数学中闭包同时包含集合A和A的边界, 变成语言中的闭包则是包含函数和函数的上下文

巧合(?)的是: 函数是一种关系, 而关系本质上是一个集合