浅谈Python装饰器

转载自:http://blog.csdn.net/mdl13412/article/details/22608283

前置知识

一级对象

Python将一切视为 objec t的子类,即一切都是对象,因此函数可以像变量一样被指向和传递,我们来看下面的例子:

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def foo():  
  2.     pass  
  3.       
  4. print issubclass(foo.__class__, object)  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. True  
上述代码说明了Python 中的函数是 object 的子类,下面让我们看函数被当作参数传递时的效果:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def foo(func):  
  2.     func()  
  3.   
  4. def bar():  
  5.     print "bar"  
  6.   
  7. foo(bar)  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. bar  

Python中的namespace

Python中通过提供 namespace 来实现重名函数/方法、变量等信息的识别,其一共有三种 namespace,分别为:

  • local namespace: 作用范围为当前函数或者类方法
  • global namespace: 作用范围为当前模块
  • build-in namespace: 作用范围为所有模块

当函数/方法、变量等信息发生重名时,Python会按照 local namespace -> global namespace -> build-in namespace的顺序搜索用户所需元素,并且以第一个找到此元素的 namespace 为准。
下面以系统的 build-in 函数 str 为例进行说明:

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def str(s):  
  2.     print "global str()"  
  3.   
  4. def foo():  
  5.     def str(s):  
  6.         print "closure str()"  
  7.     str("dummy")  
  8.   
  9. def bar():  
  10.     str("dummy")  
  11.   
  12. foo()  
  13. bar()  
首先定义三个 global namespace 的函数 strfoo 和bar,然后在 foo 函数中定义一个内嵌的 local namespace 的函数str,然后在函数 foo 和bar 中分别调用 str("dummy"),其运行结果如下所示:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. closure str()  
  2. global str()  
通过编码实验,我们可以看到:
  • foo 中调用 str 函数时,首先搜索 local namespace,并且成功找到了所需的函数,停止搜索,使用此namespace 中的定义
  • bar 中调用 str 函数时,首先搜索 local namespace,但是没有找到str 方法的定义,因此继续搜索 global namespace,并成功找到了str 的定义,停止搜索,并使用此定义
下面我们使用Python内置的 `ocals() 和 globals() 函数查看不同 namespace 中的元素定义:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. var = "var in global"  
  2.   
  3. def fun():  
  4.     var = "var in fun"  
  5.     print "fun: " + str(locals())  
  6.   
  7. print "globals: " + str(globals())  
  8. fun()  
运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. globals: {'__builtins__': <module '__builtin__' (built-in)>, '__file__''a.py''__package__'None'fun': <function fun at 0x7f2ca74f66e0>, 'var''var in global''__name__''__main__''__doc__'None}  
  2. fun: {'var''var in fun'}  
通过运行结果,我们看到了 fun 定义了 local namespace 的变量var,在 global namespace 有一个全局的 var 变量,那么当在global namespace 中直接访问 var 变量的时候,将会得到 var = "var in global" 的定义,而在fun 函数的 local namespace 中访问 var 变量,则会得到fun 私有的 var = "var in fun" 定义。

*args and **kwargs

  • *args: 把所有的参数按出现顺序打包成一个 list
  • **kwargs:把所有 key-value 形式的参数打包成一个 dict

下面给出一个 *args 的例子:

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. params_list = (12)  
  2. params_tupple = (12)  
  3.   
  4. def add(x, y):  
  5.     print x + y  
  6.   
  7. add(*params_list)  
  8. add(*params_tupple)  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. 3  
  2. 3  
**kwargs 的例子:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. params = {  
  2.     'x'1,  
  3.     'y'2  
  4. }  
  5.   
  6. def add(x, y):  
  7.     print x + y  
  8.   
  9. add(**params)  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. 3  

闭包

[plain] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. 闭包在维基百科上的定义如下: 在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。  
下面给出一个使用闭包实现的logger factory的例子:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def logger_facroty(prefix="", with_prefix=True):  
  2.     if with_prefix:  
  3.         def logger(msg):  
  4.             print prefix + msg  
  5.         return logger  
  6.     else:  
  7.         def logger(msg):  
  8.             print msg  
  9.         return logger  
  10.   
  11. logger_with_prefix = logger_facroty("Prefix: ")  
  12. logger_without_prefix = logger_facroty(with_prefix=False)  
  13. logger_with_prefix("msg")  
  14. logger_without_prefix("msg")  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. Prefix: msg  
  2. msg  
在上面这个闭包的例子中,prefix 变量时所谓的自由变量,其在 return logger 执行完毕后,便脱离了创建它的环境logger_factory,但因为其被logger_factory 中定义的 logger 函数所引用,其生命周期将至少和 logger 函数相同。这样,在 logger 中就可以引用到logger_factory 作用域内的变量 prefix
将闭包与 namespace 结合起来:

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. var = "var in global"  
  2.   
  3. def fun_outer():  
  4.     var = "var in fun_outer"  
  5.     unused_var = "this var is not used in fun_inner"  
  6.     print "fun_outer: " + var  
  7.     print "fun_outer: " + str(locals())  
  8.     print "fun_outer: " + str(id(var))  
  9.   
  10.     def fun_inner():  
  11.         print "fun_inner: " + var  
  12.         print "fun_inner: " + str(locals())  
  13.         print "fun_inner: " + str(id(var))  
  14.   
  15.     return fun_inner  
  16.   
  17. fun_outer()()  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. fun_outer: var in fun_outer  
  2. fun_outer: {'var''var in fun_outer''unused_var''this var is not used in fun_inner'}  
  3. fun_outer: 140228141915584  
  4. fun_inner: var in fun_outer  
  5. fun_inner: {'var''var in fun_outer'}  
  6. fun_inner: 140228141915584  
在这个例子中,当 fun_outer 被定义时,其内部的定义的 fun_inner 函数对 print "fun_inner: " + var中所引用的 var 变量进行搜索,发现第一个被搜索到的 var 定义在 fun_outer 的 local namespace 中,因此使用此定义,通过 print "fun_outer: " + str(id(var)) 和 print "fun_inner: " + str(id(var)),当var 超出 fun_outer 的作用域后,依然存活,而 fun_outer 中的unused_var 变量由于没有被 fun_inner 所引用,因此会被GC

探索装饰器

定义

[plain] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. 装饰器在维基百科上的定义如下: A decorator is any callable Python object that is used to modify a function, method or class definition.   

基本语法

语法糖

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. @bar  
  2. def foo():  
  3.     print "foo"  
其等价于:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def foo():  
  2.     print "foo"  
  3. foo = bar(foo)  

无参数装饰器

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def foo(func):  
  2.     print 'decorator foo'  
  3.     return func  
  4.  
  5. @foo  
  6. def bar():  
  7.     print 'bar'  
  8.   
  9. bar()  
foo 函数被用作装饰器,其本身接收一个函数对象作为参数,然后做一些工作后,返回接收的参数,供外界调用。

注意: 时刻牢记 @foo 只是一个语法糖,其本质是 foo = bar(foo)

带参数装饰器

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. import time  
  2.   
  3. def function_performance_statistics(trace_this=True):  
  4.     if trace_this:  
  5.        def performace_statistics_delegate(func):  
  6.             def counter(*args, **kwargs):  
  7.                 start = time.clock()  
  8.                 func(*args, **kwargs)  
  9.                 end =time.clock()  
  10.                 print 'used time: %d' % (end - start, )  
  11.             return counter  
  12.     else:  
  13.        def performace_statistics_delegate(func):  
  14.             return func  
  15.     return performace_statistics_delegate  
  16.  
  17. @function_performance_statistics(True)  
  18. def add(x, y):  
  19.     time.sleep(3)  
  20.     print 'add result: %d' % (x + y,)  
  21.  
  22. @function_performance_statistics(False)  
  23. def mul(x, y=1):  
  24.     print 'mul result: %d' % (x * y,)  
  25.   
  26. add(11)  
  27. mul(10)  
上述代码想要实现一个性能分析器,并接收一个参数,来控制性能分析器是否生效,其运行效果如下所示:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. add result: 2  
  2. used time: 0  
  3. mul result: 10  
上述代码中装饰器的调用等价于:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. add = function_performance_statistics(True)(add(11))  
  2. mul = function_performance_statistics(False)(mul(10))  

类的装饰器

类的装饰器不常用,因此只简单介绍。

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def bar(dummy):  
  2.     print 'bar'  
  3.   
  4. def inject(cls):  
  5.     cls.bar = bar  
  6.     return cls  
  7.  
  8. @inject  
  9. class Foo(object):  
  10.     pass  
  11.   
  12. foo = Foo()  
  13. foo.bar()  
上述代码的 inject 装饰器为类动态的添加一个 bar 方法,因为类在调用非静态方法的时候会传进一个self 指针,因此 bar 的第一个参数我们简单的忽略即可,其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. bar  

类装饰器

类装饰器相比函数装饰器,具有灵活度大,高内聚、封装性等优点。其实现起来主要是靠类内部的 __call__ 方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法,下面时一个实例:

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. class Foo(object):  
  2.     def __init__(self, func):  
  3.         super(Foo, self).__init__()  
  4.         self._func = func  
  5.   
  6.     def __call__(self):  
  7.         print 'class decorator'  
  8.         self._func()  
  9.  
  10. @Foo  
  11. def bar():  
  12.     print 'bar'  
  13.   
  14. bar()  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. class decorator  
  2. bar  

内置装饰器

Python中内置的装饰器有三个: staticmethodclassmethod 和property

staticmethod 是类静态方法,其跟成员方法的区别是没有 self 指针,并且可以在类不进行实例化的情况下调用,下面是一个实例,对比静态方法和成员方法

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. class Foo(object):  
  2.     @staticmethod  
  3.     def statc_method(msg):  
  4.         print msg  
  5.   
  6.     def member_method(self, msg):  
  7.         print msg  
  8.   
  9. foo = Foo()  
  10. foo.member_method('some msg')  
  11. foo.statc_method('some msg')  
  12. Foo.statc_method('some msg')  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. some msg  
  2. some msg  
  3. some msg  
classmethod 与成员方法的区别在于所接收的第一个参数不是 self 类实例的指针,而是当前类的具体类型,下面是一个实例:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. class Foo(object):  
  2.     @classmethod  
  3.     def class_method(cls):  
  4.         print repr(cls)  
  5.   
  6.     def member_method(self):  
  7.         print repr(self)  
  8.   
  9. foo = Foo()  
  10. foo.class_method()  
  11. foo.member_method()  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. <class '__main__.Foo'>  
  2. <__main__.Foo object at 0x10a611c50>  
property 是属性的意思,即可以通过通过类实例直接访问的信息,下面是具体的例子:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. class Foo(object):  
  2.     def __init__(self, var):  
  3.         super(Foo, self).__init__()  
  4.         self._var = var  
  5.  
  6.     @property  
  7.     def var(self):  
  8.         return self._var  
  9.  
  10.     @var.setter  
  11.     def var(self, var):  
  12.         self._var = var  
  13.   
  14. foo = Foo('var 1')  
  15. print foo.var  
  16. foo.var = 'var 2'  
  17. print foo.var  
注意: 如果将上面的 @var.setter 装饰器所装饰的成员函数去掉,则Foo.var 属性为只读属性,使用 foo.var = 'var 2' 进行赋值时会抛出异常,其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. var 1  
  2. var 2  
注意: 如果使用老式的Python类定义,所声明的属性不是 read only的,下面代码说明了这种情况:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. class Foo:  
  2.     def __init__(self, var):  
  3.         self._var = var  
  4.  
  5.     @property  
  6.     def var(self):  
  7.         return self._var  
  8.   
  9. foo = Foo('var 1')  
  10. print foo.var  
  11. foo.var = 'var 2'  
  12. print foo.var  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. var 1  
  2. var 2  

调用顺序

装饰器的调用顺序与使用 @ 语法糖声明的顺序相反,如下所示:

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def decorator_a(func):  
  2.     print "decorator_a"  
  3.     return func  
  4.   
  5. def decorator_b(func):  
  6.     print "decorator_b"  
  7.     return func  
  8.  
  9. @decorator_a  
  10. @decorator_b  
  11. def foo():  
  12.     print "foo"  
  13.       
  14. foo()  
其等价于:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def decorator_a(func):  
  2.     print "decorator_a"  
  3.     return func  
  4.   
  5. def decorator_b(func):  
  6.     print "decorator_b"  
  7.     return func  
  8.   
  9. def foo():  
  10.     print "foo"  
  11.   
  12. foo = decorator_a(decorator_b(foo))  
  13. foo()  
通过等价的调用形式我们可以看到,按照python的函数求值序列,decorator_b(fun) 会首先被求值,然后将其结果作为输入,传递给decorator_a,因此其调用顺序与声明顺序相反。其运行结果如下所示:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. decorator_b  
  2. decorator_a  
  3. foo  

调用时机

装饰器很好用,那么它什么时候被调用?性能开销怎么样?会不会有副作用?接下来我们就以几个实例来验证我们的猜想。
首先我们验证一下装饰器的性能开销,代码如下所示:

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def decorator_a(func):  
  2.     print "decorator_a"  
  3.     print 'func id: ' + str(id(func))  
  4.     return func  
  5.   
  6. def decorator_b(func):  
  7.     print "decorator_b"  
  8.     print 'func id: ' + str(id(func))  
  9.     return func  
  10.   
  11. print 'Begin declare foo with decorators'  
  12. @decorator_a  
  13. @decorator_b  
  14. def foo():  
  15.     print "foo"  
  16. print 'End declare foo with decorators'  
  17.   
  18. print 'First call foo'  
  19. foo()  
  20. print 'Second call foo'  
  21. foo()  
  22. print 'Function infos'  
  23. print 'decorator_a id: ' + str(id(decorator_a))  
  24. print 'decorator_b id: ' + str(id(decorator_b))  
  25. print 'fooid : ' + str(id(foo))  
其运行结果如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. Begin declare foo with decorators  
  2. decorator_b  
  3. func id: 140124961990488  
  4. decorator_a  
  5. func id: 140124961990488  
  6. End declare foo with decorators  
  7. First call foo  
  8. foo  
  9. Second call foo  
  10. foo  
  11. Function infos  
  12. decorator_a id: 140124961954464  
  13. decorator_b id: 140124961988808  
  14. fooid : 140124961990488  
在运行结果中的:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. Begin declare foo with decorators  
  2. decorator_b  
  3. func id: 140124961990488  
  4. decorator_a  
  5. func id: 140124961990488  
  6. End declare foo with decorators  
证实了装饰器的调用时机为: 被装饰对象定义时
而运行结果中的:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. First call foo  
  2. foo  
  3. Second call foo  
  4. foo  
证实了在相同 .py 文件中,装饰器对所装饰的函数只进行一次装饰,不会每次调用相应函数时都重新装饰,这个很容易理解,因为其本质等价于下面的函数签名重新绑定:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. foo = decorator_a(decorator_b(foo))  
对于跨模块的调用,我们编写如下结构的测试代码:
[ruby] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. .  
  2. ├── common  
  3. │   ├── decorator.py  
  4. │   ├── __init__.py  
  5. │   ├── mod_a  
  6. │   │   ├── fun_a.py  
  7. │   │   └── __init__.py  
  8. │   └── mod_b  
  9. │       ├── fun_b.py  
  10. │       └── __init__.py  
  11. └── test.py  
上述所有模块中的 __init__.py 文件均为: # -*- coding: utf-8 -*-
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. # -*- coding: utf-8 -*-  
  2. # common/mod_a/fun_a.py  
  3.   
  4.   
  5. from common.decorator import foo  
  6.   
  7.   
  8. def fun_a():  
  9.     print 'in common.mod_a.fun_a.fun_a call foo'  
  10.     foo()  

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. # -*- coding: utf-8 -*-  
  2. # common/mod_b/fun_b.py  
  3.   
  4. from common.decorator import foo  
  5.   
  6. def fun_b():  
  7.     print 'in common.mod_b.fun_b.fun_b call foo'  
  8.     foo()  
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. # -*- coding: utf-8 -*-  
  2. # common/decorator.py  
  3.   
  4. def decorator_a(func):  
  5.     print 'init decorator_a'  
  6.     return func  
  7.  
  8. @decorator_a  
  9. def foo():  
  10.     print 'function foo'  
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. # -*- coding: utf-8 -*-  
  2. # test.py  
  3.   
  4. from common.mod_a.fun_a import fun_a  
  5. from common.mod_b.fun_b import fun_b  
  6.   
  7. fun_a()  
  8. fun_b()  
上述代码通过创建 common.mod_a 和 common.mod_b 两个子模块,并调用common.decorator 中的 foo 函数,来测试跨模块时装饰器的工作情况,运行 test.py 的结果如下所示:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. init decorator_a  
  2. in common.mod_a.fun_a.fun_a call foo  
  3. function foo  
  4. in common.mod_b.fun_b.fun_b call foo  
  5. function foo  
经过上面的验证,可以看出,对于跨模块的调用,装饰器也只会初始化一次,不过这要归功于 *.pyc,这与本文主题无关,故不详述。
关于装饰器副作用的话题比较大,这不仅仅是装饰器本身的问题,更多的时候是我们设计上的问题,下面给出一个初学装饰器时大家都会遇到的一个问题——丢失函数元信息:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def decorator_a(func):  
  2.     def inner(*args, **kwargs):  
  3.         res = func(*args, **kwargs)  
  4.         return res  
  5.     return inner  
  6.  
  7. @decorator_a  
  8. def foo():  
  9.     '''''foo doc'''  
  10.     return 'foo result'  
  11.   
  12. print 'foo.__module__: ' + str(foo.__module__)  
  13. print 'foo.__name__: ' + str(foo.__name__)  
  14. print 'foo.__doc__: ' + str(foo.__doc__)  
  15. print foo()  
其运行结果如下所示:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. foo.__module__: __main__  
  2. foo.__name__: inner  
  3. foo.__doc__: None  
  4. foo result  
我们可以看到,在使用 decorator_a 对 foo 函数进行装饰后,foo 的元信息会丢失,解决方案参见: functools.wraps

多个装饰器运行期行为

前面已经讲解过装饰器的调用顺序和调用时机,但是被多个装饰器装饰的函数,其运行期行为还是有一些细节需要说明的,而且很可能其行为会让你感到惊讶,下面时一个实例:

[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def tracer(msg):  
  2.     print "[TRACE] %s" % msg  
  3.   
  4. def logger(func):  
  5.     tracer("logger")  
  6.     def inner(username, password):  
  7.         tracer("inner")  
  8.         print "call %s" % func.__name__  
  9.         return func(username, password)  
  10.     return inner  
  11.   
  12. def login_debug_helper(show_debug_info=False):  
  13.     tracer("login_debug_helper")  
  14.     def proxy_fun(func):  
  15.         tracer("proxy_fun")  
  16.         def delegate_fun(username, password):  
  17.             tracer("delegate_fun")  
  18.             if show_debug_info:  
  19.                 print "username: %s\npassword: %s" % (username, password)  
  20.             return func(username, password)  
  21.         return delegate_fun  
  22.     return proxy_fun  
  23.   
  24. print 'Declaring login_a'  
  25.  
  26. @logger  
  27. @login_debug_helper(show_debug_info=True)  
  28. def login_a(username, password):  
  29.     tracer("login_a")  
  30.     print "do some login authentication"  
  31.     return True  
  32.   
  33. print 'Call login_a'  
  34. login_a("mdl""pwd")  
大家先来看一下运行结果,看看是不是跟自己想象中的一致:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. Declaring login_a  
  2. [TRACE] login_debug_helper  
  3. [TRACE] proxy_fun  
  4. [TRACE] logger  
  5. Call login_a  
  6. [TRACE] inner  
  7. call delegate_fun  
  8. [TRACE] delegate_fun  
  9. username: mdl  
  10. password: pwd  
  11. [TRACE] login_a  
  12. do some login authentication  
首先,装饰器初始化时的调用顺序与我们前面讲解的一致,如下:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. Declaring login_a  
  2. [TRACE] login_debug_helper  
  3. [TRACE] proxy_fun  
  4. [TRACE] logger  
然而,接下来,来自 logger 装饰器中的 inner 函数首先被执行,然后才是login_debug_helper 返回的 proxy_fun 中的 delegate_fun 函数。各位读者发现了吗,运行期执行login_a 函数的时候,装饰器中返回的函数的执行顺序是相反的,难道是我们前面讲解的例子有错误吗?其实,如果大家的认为运行期调用顺序应该与装饰器初始化阶段的顺序一致的话,那说明大家没有看透这段代码的调用流程,下面我来为大家分析一下。
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. def login_debug_helper(show_debug_info=False):  
  2.     tracer("login_debug_helper")  
  3.     def proxy_fun(func):  
  4.         tracer("proxy_fun")  
  5.         def delegate_fun(username, password):  
  6.             tracer("delegate_fun")  
  7.             if show_debug_info:  
  8.                 print "username: %s\npassword: %s" % (username, password)  
  9.             return func(username, password)  
  10.         return delegate_fun  
  11.     return proxy_fun  
当装饰器 login_debug_helper 被调用时,其等价于:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. login_debug_helper(show_debug_info=True)(login_a)('mdl''pwd')  
对于只有 login_debug_helper 的情况,现在就应该是执行玩login_a输出结果的时刻了,但是如果现在在加上logger 装饰器的话,那么这个login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')就被延迟执行,而将 login_debug_helper(show_debug_info=True)(login_a) 作为参数传递给 logger,我们令 login_tmp = login_debug_helper(show_debug_info=True)(login_a),则调用过程等价于:
[python] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. login_tmp = login_debug_helper(show_debug_info=True)(login_a)  
  2. login_a = logger(login_tmp)  
  3. login_a('mdl''pwd')  
相信大家看过上面的等价变换后,已经明白问题出在哪里了,如果你还没有明白,我强烈建议你把这个例子自己敲一遍,并尝试用自己的方式进行化简,逐步得出结论。

一些实例参考

本文主要讲解原理性的东西,具体的实例可以参考下面的链接:
Python装饰器实例:调用参数合法性验证

Python装饰器与面向切面编程

Python装饰器小结

Python tips: 超时装饰器, @timeout decorator

python中判断一个运行时间过长的函数

python利用装饰器和threading实现异步调用

python输出指定函数运行时间的装饰器

python通过装饰器和线程限制函数的执行时间

python装饰器的一个妙用

通过 Python 装饰器实现DRY(不重复代码)原则

参考资料

Understanding Python Decorators in 12 Easy Steps

Decorators and Functional Python

Python Wiki: PythonDecorators

Meta-matters: Using decorators for better Python programming

Python装饰器入门(译)

Python装饰器与面向切面编程

Python 的闭包和装饰器

Python装饰器学习(九步入门)

python 装饰器和 functools 模块

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章