轻松理解python中的闭包和装饰器(上)
继面向对象编程之后函数式编程逐渐火起来了,在python中也同样支持函数式编程,我们平时使用的map, reduce, filter等都是函数式编程的例子。在函数式编程中,函数也作为一个变量存在,对应的函数名就是这个变量的名字,我们可以重新对其赋值,例如abs=len, abs( [1,2,3] ) 返回的是数字3。既然函数是个变量那我们就可以将函数做为参数,和返回值进行传递。
首先我们讲解函数做为返回值:
1 def outFunc():
2 print 'call outFunc()...'
3
4 def inFunc():
5 print 'call inFunc()...'
6
7 return inFunc #返回定义好的函数
在上面的函数定义中,我们在函数 outFunc 内部又定义了一个函数 inFunc。准确来说是定义了一个函数对象,用 inFunc 做为它的名字。最后外层函数 outFunc 返回变量 inFunc,也就是这个函数对象。我们可以通过调用outFunc获得这个对象:
1 >>> x = outFunc() # 调用outFunc()
2 call outFunc()...
3 >>> x # 变量x是outFunc()返回的函数:
4 <function inFunc at 0x1037bf320>
5 >>> x() # x指向函数,因此可以调用
6 call inFunc()... # 调用x()就是执行inFunc()函数定义的代码
请注意区分返回函数和返回值:
1 def myabs():
2 return abs # 返回函数
3 def myabs2(x):
4 return abs(x) # 返回函数调用的结果,返回值是一个数值
通过返回函数我们可以把一些计算延迟执行。例如,如果定义一个普通的求和函数:
1 def calc_sum(lst):
2 return sum(lst)
调用calc_sum()函数时,将立刻计算并得到结果:
1 >>> calc_sum([1, 2, 3, 4])
2 10
但是,如果返回一个函数,就可以“延迟计算”:
1 def calc_sum(lst):
2 def lazy_sum():
3 return sum(lst)
4 return lazy_sum
1 >>> f = calc_sum([1, 2, 3, 4]) # 调用calc_sum()并没有计算出结果,而是返回函数:
2 >>> f
3 <function lazy_sum at 0x1037bfaa0>
4 >>> f() # 对返回的函数进行调用时,才计算出结果:
5 10
由于可以返回函数,我们在后续代码里就可以决定到底要不要调用该函数。 此外还有一个优点就是内部定义的函数无法被外部访问,例如lazy_sum函数无法被直接调用,只能通过calc_sum进行调用。
同时注意我们在lazy_sum中引用了 calc_sum 的参数 lst, 用 f 接收返回的函数,再调用 f 时我们并没有传递参数,但它仍然可以继续工作,说明 lst 已经被绑定在lazy_sum内部了,成为函数的一部分,我们调用函数自然就可以操作 lst 求得 lst的和。
像这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。
闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。举例如下:
1 # 希望一次返回3个函数,分别计算1x1,2x2,3x3:
2 def count():
3 fs = []
4 for i in range(1, 4):
5 def f():
6 return i*i
7 fs.append(f)
8 return fs
9
10 f1, f2, f3 = count() #进行调用
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果全部都是 9(请自己动手验证)。
原因就是当count()函数返回了3个函数时,这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时:
1 >>> f1()
2 9 # 因为f1现在才计算i*i,但现在i的值已经变为3
因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。
考察下面的函数 f:
1 def f(j):
2 def g():
3 return j*j
4
5 return g
它可以正确地返回一个闭包g,g所引用的变量j不是循环变量,因此将正常执行。
在count函数的循环内部,如果借助f函数,就可以避免引用循环变量 i 。
1 def count():
2 fs = []
3
4 for i in range(1, 4):
5 def f(j):
6 def g():
7 return j*j # g所引用的变量j不是循环变量,而是上层函数传递进来的参数
8 return g
9
10 r = f(i)
11 fs.append(r)
12 return fs
13
14 f1, f2, f3 = count() #进行调用
15 print f1(), f2(), f3()
讲完了函数做为返回值,下一节我们讲解函数做为参数和装饰器的相关概念,精彩继续。