轻松理解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()     

 讲完了函数做为返回值,下一节我们讲解函数做为参数和装饰器的相关概念,精彩继续

posted @ 2016-08-24 11:36  gaorong404  阅读(553)  评论(0编辑  收藏  举报