Python札记 -- 装饰器

    这几天花了点时间了解了下Python的装饰器。其实以前在书上也看过有关的内容,不过当时不理解。今天把自己的一点体会写出来跟大家分享一下。

    网上流传得比较广的,有关python装饰器的文章有两篇,一篇是CSDN上的,另外一篇是园子里的。附带链接如下:
    http://blog.csdn.net/thy38/article/details/4471421
    http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html
    我个人比较喜欢园子里的那篇,讲得很透彻,能让大家对装饰器有个大概的了解。至于CSDN那篇,我不太清楚他的python版本,他给出来的 “装饰器语法--无参数装饰器” 在我的机器上运行时是有问题的,稍后我会详细跟大家讨论。

    一、装饰器能干啥?

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

    二、如何编写自己的装饰器?

    让我们来编写一个比较简单的装饰器,在Python里面代码看起来会是这样的:

 1 #!/usr/bin/env python
 2 
 3 def deco(func):
 4     def wrapper():
 5         print "Wrap start"
 6         func()
 7         print "Wrap end\n"
 8     return wrapper
 9 
10 @deco
11 def foo():
12     print "In foo():"
13 
14 foo()

    运行起来是这个样子的:

1 $ python decorate.py
2 Wrap start
3 In foo():
4 Wrap end

    我们可以在“Wrap start”和“Wrap end”里面做一些自己想要的功能,比如计算运行时间,输出日志等等。

    三、为什么要返回一个函数?

    我第一次看到“return wrapper”这句的时候,就在想为什么要返回一个函数?
    正好CSDN上的文章是没返回函数的,让我们来仔细分析一下代码:   

1 def deco(func):  
2     print func  
3     return func  
4 @deco  
5 def foo():pass  
6 
7 foo() 

    运行的结果看起来很完美,但是只是看起来。让我们把代码修改一下:

 1 >>> def deco(func):
 2 ...     print "In deco"
 3 ...     return func
 4 ...
 5 >>> @deco
 6 ... def foo():
 7 ...     print "In foo"
 8 ...
 9 In deco
10 >>>

    等等,代码里面还没有调用 foo(),怎么就print “In deco”了?
    这里编写的装饰器根本就没有达到我们要求的功能,因为它返回的还是 func 本身,print “In deco” 也只会在初始化的时候运行一次,仅仅一次。
    让我们回到刚开始的例子,在 deco 里面返回了一个 wrapper 函数对象。可以试着这么理解,deco的作用是给 func 进行装饰,wrapper 就是被装饰过的func。
    然后我们就可以重复调用这个被装饰过的函数。


    四、怎么装饰一个 需要传参数 的函数?

    现在另一个问题又来了,前面被装饰的函数都是不带参数的,那带参数的函数要怎么装饰呢? 
    让我们先尝试下前面的办法:   

 1 >>> def deco(func):
 2 ...     def wrapper():
 3 ...         print "Wrap start"
 4 ...         func()
 5 ...         print "Wrap end\n"
 6 ...     return wrapper
 7 ...
 8 >>> @deco
 9 ... def foo(x):
10 ...     print "In foo():"
11 ...     print "I have a para: %s" % x
12 ...
13 >>> foo('x')
14 Traceback (most recent call last):
15   File "<stdin>", line 1, in <module>
16 TypeError: wrapper() takes no arguments (1 given)

    报了个缺少参数的错误,那把这个参数带上:

 1 >>> def deco(func):
 2 ...     def wrapper(x):
 3 ...         print "Wrap start"
 4 ...         func(x)
 5 ...         print "Wrap end\n"
 6 ...     return wrapper
 7 ...
 8 >>> @deco
 9 ... def foo(x):
10 ...     print "In foo():"
11 ...     print "I have a para: %s" % x
12 ...
13 >>> foo('x')
14 Wrap start
15 In foo():
16 I have a para: x
17 Wrap end

    现在可以正常传递参数了。

    五、怎么装饰 参数列表不一样 的多个函数?

    继续发散一下,要是想装饰多个函数,但是这些函数的参数列表变化很大的呢?
    这个时候,就到了使用Python参数魔法的时候了。
    定义函数时:*params:收集其余的位置参数,返回元组。    **params:收集其余的关键字参数,返回字典。
    调用函数时:*params:将元组拆分为位置参数传入。    **params:将字典拆分为关键字参数传入。

    利用上面的参数魔法后,代码看起来会是这样的:

 1 #!/usr/bin/env python
 2 
 3 def deco(func):
 4     def wrapper(*args, **kwargs):
 5         print "Wrap start"
 6         func(*args, **kwargs)
 7         print "Wrap end\n"
 8     return wrapper
 9 
10 @deco
11 def foo(x):
12     print "In foo():"
13     print "I have a para: %s" % x
14 
15 @deco
16 def bar(x,y):
17     print "In bar():"
18     print "I have two para: %s and %s" % (x, y)
19 
20 @deco
21 def foo_dict(x,z='dict_para'):
22     print "In foo_dict:"
23     print "I have two para, %s and %s" % (x, z)
24 
25 if __name__ == "__main__":
26     foo('x')
27     bar('x', 'y')
28     foo_dict('x', z='dict_para')


    运行一下看看效果:

 1 $ python decorate.py
 2 Wrap start
 3 In foo():
 4 I have a para: x
 5 Wrap end
 6 
 7 Wrap start
 8 In bar():
 9 I have two para: x and y
10 Wrap end
11 
12 Wrap start
13 In foo_dict:
14 I have two para, x and dict_para
15 Wrap end

    本人水平有限,以上如有错误,欢迎指正,谢谢大家^_^。

 

posted @ 2013-01-17 19:22  竹风抚荷塘  阅读(6233)  评论(9编辑  收藏  举报