首页 > 代码库 > 轻松理解python中的闭包和装饰器 (下)

轻松理解python中的闭包和装饰器 (下)

在 上篇 我们讲了python将函数做为返回值和闭包的概念,下面我们继续讲解函数做参数和装饰器,这个功能相当方便实用,可以极大地简化代码,就让我们go on吧!

能接受函数做参数的函数我们称之为高阶函数,例如filter, map, reduce这些函数

可以定义一个函数作为高阶函数例如:

def func(x, y, f):

         return f(x)+f(y)

可以这样调用func(2,-1,abs) 函数返回结果为3

有些时候,我们不需要显式地定义传入的函数,直接传入匿名函数更方便。

在Python中,对匿名函数提供了支持。以map()函数为例,计算 f(x)=x时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

1 >>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
2 [1, 4, 9, 16, 25, 36, 49, 64, 81]

通过对比可以看出,匿名函数 lambda x: x * x 实际上就是:

def f(x):

    return x * x

关键字lambda 表示匿名函数,冒号前面的 x 表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是该表达式的结果。

使用匿名函数,可以不必定义函数名,直接创建一个函数对象,很多时候可以简化代码:

1 >>> sorted([1, 3, 9, 5, 0], lambda x,y: -cmp(x,y))
2 [9, 5, 3, 1, 0]

返回函数的时候,也可以返回匿名函数:

1 >>> myabs = lambda x: -x if x < 0 else x
2 >>> myabs(-1)
3 1
4 >>> myabs(1)
5 1

 

有些时候我们定义了一个函数,想在运行时动态增加功能,又不想改动函数本身的代码,这就可以用高阶函数实现: 可以接收一个函数作为参数对其包装,返回函数:

 1 def f1(x):         
 2     return x*2
 3 
 4 def new_fn(f):
 5     def fn(x):       # 定义一个内层函数
 6         print "call: " + f.__name__ + "()"
 7         return f(x)  # 再内层函数中调用传递进来的函数
 8      
 9     return fn        # 返回内层函数
10 
11 g1 = new_fn(f1)      #进行调用
12 print g1(2) 

执行上面代码,输出

call: f1()
4

可见我们将一个 f1 包装了一下,在它原有计算x*2的功能上又输出了自己的函数名。整个过程中我们定义的内层函数 fn 是关键,它在内部使用的是new_fn传递进来的参数 f ,这不就是前面讲的闭包吗? fn在添加其他功能后又调用了原来 f。

我们甚至可以这样

1 f1 = new_fn(f1)
2 print f1(2)

上面的代码输出结果一样,但是将f1传递进去又得到一个f1, 一样的调用方法但新的功能比以前的那个高级。

上面代码中f1 = new_fn(f1)这样的写法未免太过蹩脚,我们可以利用python提供的 装饰器 语法优雅的实现。上面new_fn的定义不变,我们只需要重新定义 f1 就可以了,如下:

 1 @new_fn
 2 def f1(x):         
 3     return x*2
 4 
 5 @new_fn
 6 def f2(x):
 7     return x/2
 8 
 9 print f1(2)   
10 print f2(2)  

输出如下:

call: f1()
4
call: f2()
1

我们在函数前面加了@new_fn就代表了对f1这个函数进行装饰加工,以后调用f1就直接调用了加工后的f1, 对于f2也这样,用起来一切很方便。

注意上面的代码只能对 f1 这样单个参数的函数能进行装饰,如果需要装饰两个参数的函数那new_fn就无法实现了,其实也很简单,我们只需要使用*args, **kw,就能保证任意个数的参数总是能正常调用,如下。

 1 def new_fn(f):
 2     def fn(*args, **kw):    # 可以传入任意参数
 3         print ‘call ‘ + f.__name__ + ‘()...‘
 4         return f(*args, **kw)
 5     return fn
 6 
 7 @new_fn
 8 def add(x, y):
 9     return x + y 
10 
11 
12 print add(1, 2)

其实仔细观察我们发现虽然调用add传递了参数1,2, 但是它只是传递到了装饰器中,也就是上面的 fn 中,我们可以随时在装饰器中改变它的参数然后再传递给 f。

经过上面的步骤现在总算是可以正常实现功能了。但是如果我们再提出需求呐(程序员总是害怕这一点??)

对于上面的 @new_fn装饰器:

1 def new_fn(f):
2     def fn(*args, **kw):  
3         print ‘call ‘ + f.__name__ + ‘()...‘
4         return f(*args, **kw)
5     return fn

发现对于被装饰的函数,new_fn 打印的语句是不能变的(除了函数名)。

如果有的函数非常重要,希望打印出‘[INFO] call xxx()...‘,有的函数不太重要,希望打印出‘[DEBUG] call xxx()...‘,这时,new_fn函数本身就需要传入‘INFO‘或‘DEBUG‘这样的参数,类似这样:

1 @new_fn(‘DEBUG‘)
2 def my_func():
3     pass

把上面的定义翻译成高阶函数的调用,就是:

1 my_func = new_fn(‘DEBUG‘)(my_func)

上面的语句看上去还是比较绕,再展开一下:

1 newfn_decorator = new_fn(‘DEBUG‘)
2 my_func = newfn_decorator(my_func)

上面的语句又相当于:

1 log_decorator = new_fn(‘DEBUG‘)
2 @log_decorator
3 def my_func():
4     pass

所以,带参数的new_fn函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数:

 1 def new_fn(prefix):
 2     def newfn_decorator(f):
 3         def wrapper(*args, **kw):
 4             print ‘[%s] %s()...‘ % (prefix, f.__name__)
 5             return f(*args, **kw)
 6         return wrapper
 7     return newfn_decorator
 8  
 9 @new_fn(‘DEBUG‘)
10 def test():
11     pass
12 print test()

执行结果如下:

[DEBUG] test()...
None

对于这种3层嵌套的decorator定义,你可以先把它拆开:

 1 # 标准decorator:
 2 def newfn_decorator(f):
 3     def wrapper(*args, **kw):
 4         print ‘[%s] %s()...‘ % (prefix, f.__name__)
 5         return f(*args, **kw)
 6     return wrapper
 7 return newfn_decorator
 8  
 9 # 返回decorator:
10 def new_fn(prefix):
11     return newfn_decorator(f)

拆开以后会发现,调用会失败,因为在3层嵌套的decorator定义中,最内层的wrapper引用了最外层的参数prefix,所以,把一个闭包拆成普通的函数调用会比较困难。不支持闭包的编程语言要实现同样的功能就需要更多的代码。

使用装饰器我们可以可以极大地简化代码,避免每个函数编写重复性代码, 实现 打印日志(@log),检测性能(@perfirmance),数据库事务(@transaction),URL路由(@post(‘/register’))等功能,而使用带参数的装饰器我们可以对装饰器本身进行一些加工,预处理等。

 

轻松理解python中的闭包和装饰器 (下)