Python 装饰器

关于decorator:

1. 函数的decorator可以是函数,也可以是类, 如果是类,装饰之后,这个函数就成为了类的一个实例

2. 类的decorator 可以是函数,也可以是类,

 

 

python特殊函数 __call__()

__call__
在Python中,函数其实是一个对象:

>>> f = abs
>>> f.__name__
'abs'
>>> f(-123)
123
由于 f 可以被调用,所以,f 被称为可调用对象。

所有的函数都是可调用对象。

一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()。

我们把 Person 类变成一个可调用对象:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

    def __call__(self, friend):
        print 'My name is %s...' % self.name
        print 'My friend is %s...' % friend
现在可以对 Person 实例直接调用:

>>> p = Person('Bob', 'male')
>>> p('Tim')
My name is Bob...
My friend is Tim...
单看 p('Tim') 你无法确定 p 是一个函数还是一个类实例,所以,在Python中,函数也是对象,对象和函数的区别并不显著。

任务
改进一下前面定义的斐波那契数列:

class Fib(object):
    ???
请加一个__call__方法,让调用更简单:

>>> f = Fib()
>>> print f(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
复制代码

可以把实例对象用类似函数的形式表示,进一步模糊了函数和对象之间的概念

复制代码
class Fib(object):
    def __init__(self):
        pass
    def __call__(self,num):
        a,b = 0,1;
        self.l=[]
        
        for i in range (num):
            self.l.append(a)
            a,b= b,a+b
        return self.l
    def __str__(self):
        return str(self.l)
    __rept__=__str__
            
f = Fib()
print f(10)
复制代码

 

 

装饰器基础

前面快速介绍了装饰器的语法,在这里,我们将深入装饰器内部工作机制,更详细更系统地介绍装饰器的内容,并学习自己编写新的装饰器的更多高级语法。

=================================================================================

什么是装饰器
装饰是为函数和类指定管理代码的一种方式。Python装饰器以两种形式呈现:
【1】函数装饰器在函数定义的时候进行名称重绑定,提供一个逻辑层来管理函数和方法或随后对它们的调用。
【2】类装饰器在类定义的时候进行名称重绑定,提供一个逻辑层来管理类,或管理随后调用它们所创建的实例。
简而言之,装饰器提供了一种方法,在函数和类定义语句的末尾插入自动运行的代码——对于函数装饰器,在def的末尾;对于类装饰器,在class的末尾。这样的代码可以扮演不同的角色。
装饰器提供了一些和代码维护性和审美相关的有点。此外,作为结构化工具,装饰器自然地促进了代码封装,这减少了冗余性并使得未来变得更容易。

=================================================================================

函数装饰器
通过在一个函数的def语句的末尾运行另一个函数,把最初的函数名重新绑定到结果。

----------------------------------------------------------------------------------------------------------------------------------------------

用法
装饰器在紧挨着定义一个函数或方法的def语句之前的一行编写,并且它由@符号以及紧随其后的对于元函数的一个引用组成——这是管理另一个函数的一个函数(或其他可调用对象)。
在编码上,函数装饰器自动将如下语法:

 

[python] view plain copy
 
  1. @decorator  
  2. def F(arg):  
  3.     ...  
  4. F(99)  

映射为这个对等形式:

 

[python] view plain copy
 
  1. def F(arg):  
  2.     ...  
  3. F = decorator(F)  
  4. F(99)  

这里的装饰器是一个单参数的可调用对象,它返回与F具有相同数目的参数的一个可调用对象。
当随后调用F函数的时候,它自动调用装饰器所返回的对象。

换句话说,装饰实际把如下的第一行映射为第二行(尽管装饰器只在装饰的时候运行一次)

[python] view plain copy
 
  1. fun(6,7)  
  2. decorator(func)(6,7)  

这一自动名称重绑定也解释了之前介绍的静态方法和property装饰器语法的原因:

[python] view plain copy
 
  1. class C:  
  2.     @staticmethod  
  3.     def meth(...):...  
  4.      
  5.     @property  
  6.     def name(self):...  

----------------------------------------------------------------------------------------------------------------------------------------------
实现
装饰器自身是返回可调用对象的可调用对象。实际上,它可以是任意类型的可调用对象,并且返回任意类型的可调用对象:函数和类的任何组合都可以使用,尽管一些组合更适合于特定的背景。

有一种常用的编码模式——装饰器返回了一个包装器,包装器把最初的函数保持到一个封闭的作用域中:

[python] view plain copy
 
  1. def decorator(F):  
  2.     def wrapper(*args):  
  3.         # 使用 F 和 *args  
  4.         # 调用原来的F(*args)  
  5.     return wrapper  
  6.  
  7. @decorator  
  8. def func(x,y):  
  9.     ...  
  10.   
  11. func(6,7)  

当随后调用名称func的时候,它确实调用装饰器所返回的包装器函数;随后包装器函数可能运行最初的func,因为它在一个封闭的作用域中仍然可以使用。

为了对类做同样的事情,我们可以重载调用操作:

[python] view plain copy
 
  1. class decorator:  
  2.     def __init__(self,func):  
  3.         self.func = func  
  4.     def __call__(self,*args):  
  5.         # 使用self.func和args  
  6.         # self.func(*args)调用最初的func  
  7.  
  8. @decorator  
  9. def func(x,y):  
  10.     ...  
  11. func(6,7)  

但是,要注意的是,基于类的代码中,它对于拦截简单函数有效,但当它应用于类方法函数时,并不很有效:
如下反例:

[python] view plain copy
 
  1. class decorator:  
  2.     def __init__(self,func):  
  3.         self.func = func  
  4.     def __call__(self,*args):                           #注意, class decorator 必须包含__call__函数,不然作为装饰器,运行的时候会报错:  TypeError: 'decorator' object is not callable
  5.         # 调用self.func(*args)失败,因为C实例参数无法传递  
  6.   
  7. class C:  
  8.     @decorator  
  9.     def method(self,x,y):  
  10.         ...  

这时候装饰的方法重绑定到一个类的方法上,而不是一个简单的函数,这一点带来的问题是,当装饰器的方法__call__随后运行的时候,其中的self接受装饰器类实例,并且类C的实例不会包含到一个*args中。

 

这时候,嵌套函数的替代方法工作得更好:

[python] view plain copy
 
  1. def decorator:  
  2.     def warpper(*args):  
  3.         # ...  
  4.     return wrapper  
  5.  
  6. @decorator  
  7. def func(x,y):  
  8.     ...  
  9. func(6,7)  
  10.   
  11. class C:  
  12.     @decorator  
  13.     def method(self,x,y):  
  14.         ...  
  15.   
  16. x = C()  
  17. x.method(6,7)  

=================================================================================

类装饰器

类装饰器与函数装饰器使用相同的语法和非常相似的编码方式。类装饰器是管理类的一种方式,或者用管理或扩展类所创建的实例的额外逻辑,来包装实例构建调用。
----------------------------------------------------------------------------------------------------------------------------------------------

用法
假设类装饰器返回一个可调用对象的一个单参数的函数,类装饰器的语法为:

 

[python] view plain copy
 
  1. @decorator  
  2. class C:  
  3.     ...  
  4. x = C(99)  

等同于下面的语法:

 

 

[python] view plain copy
 
  1. class C:  
  2.     ...  
  3. C = decorator(C)  
  4.   
  5. x = C(99)  

直接效果是随后调用类名会创建一个实例,该实例会触发装饰器所返回的可调用对象,而不是调用最初的类自身。
----------------------------------------------------------------------------------------------------------------------------------------------

实现
类装饰器返回的可调用对象,通常创建并返回最初的类的一个新的实例,以某种方式来扩展对其接口的管理。例如,下面的实例插入一个对象来拦截一个类实例的未定义的属性:

[python] view plain copy
 
  1. def decorator(cls):  
  2.     class Wrapper:  
  3.         def __init__(self,*args):  
  4.             self.wrapped = cls(*args)  
  5.         def __getattr__(self,name):  
  6.             return getattr(self.wrapped,name)  
  7.     return Wrapper  
  8.  
  9. @decorator  
  10. class C:                # C = decorator(C)  
  11.     def __init__(self,x,y):     # Run by Wrapper.__init__  
  12.         self.attr = 'spam'  
  13.   
  14. x = C(6,7)              # 等价于Wrapper(6,7)  
  15. print(x.attr)  

在这个例子中,装饰器把类的名称重新绑定到另一个类,这个类在一个封闭的作用域中保持了最初的类。

就像函数装饰器一样,类装饰器通常可以编写为一个创建并返回可调用对象的“工厂”函数。
=================================================================================

 

装饰器嵌套
有时候,一个装饰器不够,装饰器语法允许我们向一个装饰器的函数或方法添加包装器逻辑的多个层。这种形式的装饰器的语法为:

 

[python] view plain copy
 
  1. @A  
  2. @B  
  3. @C  
  4. def f(...):  
  5.     ...  

如下这样转换:

[python] view plain copy
 
  1. def f(...):  
  2.     ...  
  3. f = A(B(C(f)))  

这里,最初的函数通过3个不同的装饰器传递,每个装饰器处理前一个结果。
=================================================================================

 

装饰器参数
函数装饰器和类装饰器都能接受参数,如下:

 

[python] view plain copy
 
  1. @decorator(A,B)  
  2. def F(arg):  
  3.     ...  
  4. F(99)  

自动映射到其对等形式:

[python] view plain copy
 
  1. def F(arg):  
  2.     ...  
  3. F = decorator(A,B)(F)  
  4.   
  5. F(99)  

装饰器参数在装饰之前就解析了,并且它们通常用来保持状态信息供随后的调用使用。例如,这个例子中的装饰器函数,可能采用如下形式:

[python] view plain copy
 
  1. def decorator(A,B):  
  2.     # 保存或使用A和B  
  3.     def actualDecorator(F):  
  4.         # 保存或使用函数 F  
  5.         # 返回一个可调用对象  
  6.         return callable  
  7.     return actualDecorator  

 

=================================================================================

以上,这是装饰器的基础知识,接下来将学习编写自己的装饰器

 

编写函数装饰器
本节主要介绍编写函数装饰器的相关内容。
----------------------------------------------------------------------------------------------------------------------------------
跟踪调用

如下代码定义并应用一个函数装饰器,来统计对装饰的函数的调用次数,并且针对每一次调用打印跟踪信息。

 

[python] view plain copy
 
  1. class tracer:  
  2.     def __init__(self,func):  
  3.         self.calls = 0  
  4.         self.func = func  
  5.     def __call__(self,*args):  
  6.         self.calls += 1  
  7.         print('call %s to %s' %(self.calls, self.func.__name__))  
  8.         self.func(*args)  
  9.  
  10. @tracer  
  11. def spam(a, b, c):  
  12.     print(a + b + c)  

这是一个通过类装饰的语法写成的装饰器,测试如下:

 

 

[python] view plain copy
 
  1. >>> spam(1,2,3)  
  2. call 1 to spam  
  3. 6  
  4. >>> spam('a','b','c')  
  5. call 2 to spam  
  6. abc  
  7. >>> spam.calls  
  8. 2  
  9. >>> spam  
  10. <__main__.tracer object at 0x03098410>  

运行的时候,tracer类和装饰的函数分开保存,并且拦截对装饰的函数的随后的调用,以便添加一个逻辑层来统计和打印每次调用。
装饰之后,spam实际上是tracer类的一个实例


@装饰器语法避免了直接地意外调用最初的函数。考虑如下所示的非装饰器的对等代码:

[python] view plain copy
 
  1. calls = 0  
  2. def tracer(func,*args):  
  3.     global calls  
  4.     calls += 1  
  5.     print('call %s to %s'%(calls,func.__name__))  
  6.     func(*args)  
  7.   
  8. def spam(a,b,c):  
  9.     print(a+b+c)  

测试如下:

[python] view plain copy
 
  1. >>> spam(1,2,3)  
  2. 6  
  3. >>> tracer(spam,1,2,3)  
  4. call 1 to spam  
  5. 6  

这一替代方法可以用在任何函数上,且不需要特殊的@语法,但是和装饰器版本不同,它在代码中调用函数的每个地方都需要额外的语法。尽管装饰器不是必需的,但是它们通常是最为方便的。

 

----------------------------------------------------------------------------------------------------------------------------------
扩展——支持关键字参数

下述代码时前面例子的扩展版本,添加了对关键字参数的支持:

 

[python] view plain copy
 
  1. class tracer:  
  2.     def __init__(self,func):  
  3.         self.calls = 0  
  4.         self.func = func  
  5.     def __call__(self,*args,**kargs):  
  6.         self.calls += 1  
  7.         print('call %s to %s' %(self.calls, self.func.__name__))  
  8.         self.func(*args,**kargs)  
  9.  
  10. @tracer  
  11. def spam(a, b, c):  
  12.     print(a + b + c)  
  13.  
  14. @tracer  
  15. def egg(x,y):  
  16.     print(x**y)  

测试如下:

[python] view plain copy
 
  1. >>> spam(1,2,3)  
  2. call 1 to spam  
  3. 6  
  4. >>> spam(a=4,b=5,c=6)  
  5. call 2 to spam  
  6. 15  
  7. >>> egg(2,16)  
  8. call 1 to egg  
  9. 65536  
  10. >>> egg(4,y=4)  
  11. call 2 to egg  
  12. 256  

也可以看到,这里的代码同样使用【类实例属性】来保存状态,即调用的次数self.calls。包装的函数和调用计数器都是针对每个实例的信息。
----------------------------------------------------------------------------------------------------------------------------------

 

使用def函数语法写装饰器
使用def定义装饰器函数也可以实现相同的效果。但是有一个问题,我们也需要封闭作用域中的一个计数器,它随着每次调用而更改。我们可以很自然地想到全局变量,如下:

 

[python] view plain copy
 
  1. calls = 0  
  2. def tracer(func):  
  3.     def wrapper(*args,**kargs):  
  4.         global calls  
  5.         calls += 1  
  6.         print('call %s to %s'%(calls,func.__name__))  
  7.         return func(*args,**kargs)  
  8.     return wrapper  
  9.  
  10. @tracer  
  11. def spam(a,b,c):  
  12.     print(a+b+c)  
  13.  
  14. @tracer  
  15. def egg(x,y):  
  16.     print(x**y)  

这里calls定义为全局变量,它是跨程序的,是属于整个模块的,而不是针对每个函数的,这样的话,对于任何跟踪的函数调用,计数器都会递增,如下测试:

[python] view plain copy
 
  1. >>> spam(1,2,3)  
  2. call 1 to spam  
  3. 6  
  4. >>> spam(a=4,b=5,c=6)  
  5. call 2 to spam  
  6. 15  
  7. >>> egg(2,16)  
  8. call 3 to egg  
  9. 65536  
  10. >>> egg(4,y=4)  
  11. call 4 to egg  
  12. 256  

可以看到针对spam函数和egg函数,程序用的是同一个计数器。
----------------------------------------------------------------------------------------------------------------------------------

 

那么如何实现针对每一个函数的计数器呢,我们可以使用Python3中新增的nonlocal语句,如下:

 

[python] view plain copy
 
  1. def tracer(func):  
  2.     calls = 0  
  3.     def wrapper(*args,**kargs):  
  4.         nonlocal calls  
  5.         calls += 1  
  6.         print('call %s to %s'%(calls,func.__name__))  
  7.         return func(*args,**kargs)  
  8.     return wrapper  
  9.  
  10. @tracer  
  11. def spam(a,b,c):  
  12.     print(a+b+c)  
  13.  
  14. @tracer  
  15. def egg(x,y):  
  16.     print(x**y)  
  17.   
  18. spam(1,2,3)  
  19. spam(a=4,b=5,c=6)  
  20.   
  21. egg(2,16)  
  22. egg(4,y=4)  

运行如下:

[python] view plain copy
 
  1. call 1 to spam  
  2. 6  
  3. call 2 to spam  
  4. 15  
  5. call 1 to egg  
  6. 65536  
  7. call 2 to egg  
  8. 256  

这样,将calls变量定义在tracer函数内部,使之存在于一个封闭的函数作用域中,之后通过nonlocal语句来修改这个作用域,修改这个calls变量。如此便可以实现我们所需求的功能。

 

----------------------------------------------------------------------------------------------------------------------------------
陷阱:装饰类方法
【注意,使用类编写的装饰器不能用于装饰某一类中带self参数的的函数,这一点在Python装饰器基础中介绍过】
即如果装饰器是如下使用类编写的:

 

[python] view plain copy
 
  1. class tracer:  
  2.     def __init__(self,func):  
  3.         self.calls = 0  
  4.         self.func = func  
  5.     def __call__(self,*args,**kargs):  
  6.         self.calls += 1  
  7.         print('call %s to %s'%(self.calls,self.func.__name__))  
  8.         return self.func(*args,**kargs)  

当它装饰如下在类中的方法时:

[python] view plain copy
 
  1. class Person:  
  2.     def __init__(self,name,pay):  
  3.         self.name = name  
  4.         self.pay = pay  
  5.  
  6.     @tracer  
  7.     def giveRaise(self,percent):  
  8.         self.pay *= (1.0 + percent)  

这时程序肯定会出错。问题的根源在于,tracer类的__call__方法的self——它是一个tracer实例,当我们用__call__把装饰方法名重绑定到一个类实例对象的时候,Python只向self传递了tracer实例,它根本没有在参数列表中传递Person主体。此外,由于tracer不知道我们要用方法调用处理的Person实例的任何信息,没有办法创建一个带有一个实例的绑定的方法,所以也就没有办法正确地分配调用。

这时我们只能通过嵌套函数的方法来编写装饰器。
----------------------------------------------------------------------------------------------------------------------------------

 

计时调用
下面这个装饰器将对一个装饰的函数的调用进行计时——既有针对一次调用的时间,也有所有调用的总的时间。

 

[python] view plain copy
 
  1. import time  
  2.   
  3. class timer:  
  4.     def __init__(self,func):  
  5.         self.func = func  
  6.         self.alltime = 0  
  7.     def __call__(self,*args,**kargs):  
  8.         start = time.clock()  
  9.         result = self.func(*args,**kargs)  
  10.         elapsed = time.clock()- start  
  11.         self.alltime += elapsed  
  12.         print('%s:%.5f,%.5f'%(self.func.__name__,elapsed,self.alltime))  
  13.         return result  
  14.      
  15. @timer  
  16. def listcomp(N):  
  17.     return [x*for x in range(N)]  
  18.  
  19. @timer  
  20. def mapcall(N):  
  21.     return list(map((lambda x :x*2),range(N)))  
  22.   
  23. result = listcomp(5)  
  24. listcomp(50000)  
  25. listcomp(500000)  
  26. listcomp(1000000)  
  27. print(result)  
  28. print('allTime = %s'%listcomp.alltime)  
  29.   
  30. print('')  
  31. result = mapcall(5)  
  32. mapcall(50000)  
  33. mapcall(500000)  
  34. mapcall(1000000)  
  35. print(result)  
  36. print('allTime = %s'%mapcall.alltime)  
  37.   
  38. print('map/comp = %s '% round(mapcall.alltime/listcomp.alltime,3))  

运行结果如下:

 

 

[python] view plain copy
 
  1. listcomp:0.00001,0.00001  
  2. listcomp:0.00885,0.00886  
  3. listcomp:0.05935,0.06821  
  4. listcomp:0.11445,0.18266  
  5. [0, 2, 4, 6, 8]  
  6. allTime = 0.18266365607537918  
  7.   
  8. mapcall:0.00002,0.00002  
  9. mapcall:0.00689,0.00690  
  10. mapcall:0.08348,0.09038  
  11. mapcall:0.16906,0.25944  
  12. [0, 2, 4, 6, 8]  
  13. allTime = 0.2594409060462425  
  14. map/comp = 1.42   

这里要注意的是,map操作在Python3中返回一个迭代器,所以它的map操作不能和一个列表解析的工作直接对应,即实际上它并不花时间。所以要使用list(map())来迫使它像列表解析那样构建一个列表
----------------------------------------------------------------------------------------------------------------------------------

 

添加装饰器参数
有时我们需要装饰器来做一个额外的工作,比如提供一个输出标签并且可以打开或关闭跟踪消息。这就需要用到装饰器参数了,我们可以使用装饰器参数来制定配置选项,这些选项可以根据每个装饰的函数而编码。例如,像下面这样添加标签:

[python] view plain copy
 
  1. def timer(label = ''):  
  2.     def decorator(func):  
  3.         def onCall(*args):  
  4.             ...  
  5.             print(label,...)  
  6.         return onCall  
  7.     return decorator  
  8.  
  9. @timer('==>')  
  10. def listcomp(N):...  

我们可以将这样的结果用于计时器中,来允许在装饰的时候传入一个标签和一个跟踪控制标志。比如,下面这段代码:

[python] view plain copy
 
  1. import time  
  2.   
  3. def timer(label= '', trace=True):  
  4.     class Timer:  
  5.         def __init__(self,func):  
  6.             self.func = func  
  7.             self.alltime = 0  
  8.         def __call__(self,*args,**kargs):  
  9.             start = time.clock()  
  10.             result = self.func(*args,**kargs)  
  11.             elapsed = time.clock() - start  
  12.             self.alltime += elapsed  
  13.             if trace:  
  14.                 ft = '%s %s:%.5f,%.5f'  
  15.                 values = (label,self.func.__name__,elapsed,self.alltime)  
  16.                 print(format % value)  
  17.             return result  
  18.     return Timer  

这个计时函数装饰器可以用于任何函数,在模块中和交互模式下都可以。我们可以在交互模式下测试,如下:

[python] view plain copy
 
    1. >>> @timer(trace = False)  
    2. def listcomp(N):  
    3.     return [x * for x in range(N)]  
    4.   
    5. >>> x = listcomp(5000)  
    6. >>> x = listcomp(5000)  
    7. >>> x = listcomp(5000)  
    8. >>> listcomp  
    9. <__main__.timer.<locals>.Timer object at 0x036DCC10>  
    10. >>> listcomp.alltime  
    11. 0.0011475424533080223  
    12. >>>  
    13. >>> @timer(trace=True,label='\t=>')  
    14. def listcomp(N):  
    15.     return [x * for x in range(N)]  
    16.   
    17. >>> x = listcomp(5000)  
    18.     => listcomp:0.00036,0.00036  
    19. >>> x = listcomp(5000)  
    20.     => listcomp:0.00034,0.00070  
    21. >>> x = listcomp(5000)  
    22.     => listcomp:0.00034,0.00104  
    23. >>> listcomp.alltime  
    24. 0.0010432902706075842  

 

编写类装饰器

类装饰器类似于函数装饰器的概念,但它应用于类,它们可以用于管理类自身,或者用来拦截实例创建调用以管理实例。

-------------------------------------------------------------------------------------------------------------------------------------

单体类
由于类装饰器可以拦截实例创建调用,所以它们可以用来管理一个类的所有实例,或者扩展这些实例的接口。
下面的类装饰器实现了传统的单体编码模式,即最多只有一个类的一个实例存在。

 

[python] view plain copy
 
  1. instances = {} # 全局变量,管理实例  
  2. def getInstance(aClass, *args):  
  3.     if aClass not in instances:  
  4.         instances[aClass] = aClass(*args)  
  5.     return instances[aClass]     #每一个类只能存在一个实例  
  6.   
  7. def singleton(aClass):  
  8.     def onCall(*args):  
  9.         return getInstance(aClass,*args)  
  10.     return onCall  

为了使用它,装饰用来强化单体模型的类:

[python] view plain copy
 
  1. @singleton        # Person = singleton(Person)  
  2. class Person:  
  3.     def __init__(self,name,hours,rate):  
  4.         self.name = name  
  5.         self.hours = hours  
  6.         self.rate = rate  
  7.     def pay(self):  
  8.         return self.hours * self.rate  
  9.  
  10. @singleton        # Spam = singleton(Spam)  
  11. class Spam:  
  12.     def __init__(self,val):  
  13.         self.attr = val  
  14.           
  15. bob = Person('Bob',40,10)  
  16. print(bob.name,bob.pay())  
  17.   
  18. sue = Person('Sue',50,20)  
  19. print(sue.name,sue.pay())  
  20.   
  21. X = Spam(42)  
  22. Y = Spam(99)  
  23. print(X.attr,Y.attr)  

现在,当Person或Spam类稍后用来创建一个实例的时候,装饰器提供的包装逻辑层把实例构建调用指向了onCall,它反过来调用getInstance,以针对每个类管理并分享一个单个实例,而不管进行了多少次构建调用。
程序输出如下:

[python] view plain copy
 
  1. Bob 400  
  2. Bob 400  
  3. 42 42  

在这里,我们使用全局的字典instances来保存实例,还有一个更好的解决方案就是使用Python3中的nonlocal关键字,它可以为每个类提供一个封闭的作用域,如下:

[python] view plain copy
 
  1. def singleton(aClass):  
  2.     instance = None  
  3.     def onCall(*args):  
  4.         nonlocal instance  
  5.         if instance == None:  
  6.             instance = aClass(*args)  
  7.         return instance  
  8.     return onCall  

当然,我们也可以用类来编写这个装饰器——如下代码对每个类使用一个实例,而不是使用一个封闭作用域或全局表:

[python] view plain copy
 
  1. class singleton:  
  2.     def __init__(self,aClass):  
  3.         self.aClass = aClass  
  4.         self.instance = None  
  5.     def __call__(self,*args):  
  6.         if self.instance == None:  
  7.             self.instance = self.aClass(*args)  
  8.         return self.instance  

-------------------------------------------------------------------------------------------------------------------------------------

 

跟踪对象接口
类装饰器的另一个常用场景是每个产生实例的接口。类装饰器基本上可以在实例上安装一个包装器逻辑层,来以某种方式管理其对接口的访问。
前面,我们知道可以用__getattr__运算符重载方法作为包装嵌入到实例的整个对象接口的方法,以便实现委托编码模式。__getattr__用于拦截未定义的属性名的访问。如下例子所示:

[python] view plain copy
 
  1. class Wrapper:  
  2.     def __init__(self,obj):  
  3.         self.wrapped = obj  
  4.     def __getattr__(self,attrname):  
  5.         print('Trace:',attrname)  
  6.         return getattr(self.wrapped,attrname)  
  7.   
  8.       
  9. >>> x = Wrapper([1,2,3])  
  10. >>> x.append(4)  
  11. Trace: append  
  12. >>> x.wrapped  
  13. [1, 2, 3, 4]  
  14. >>>  
  15. >>> x = Wrapper({'a':1,'b':2})  
  16. >>> list(x.keys())  
  17. Trace: keys  
  18. ['b', 'a']  

在这段代码中,Wrapper类拦截了对任何包装对象的属性的访问,打印出一条跟踪信息,并且使用内置函数getattr来终止对包装对象的请求。

类装饰器为编写这种__getattr__技术来包装一个完整接口提供了一个替代的、方便的方法。如下:

[python] view plain copy
 
  1. def Tracer(aClass):  
  2.     class Wrapper:  
  3.         def __init__(self,*args,**kargs):  
  4.             self.fetches = 0  
  5.             self.wrapped = aClass(*args,**kargs)  
  6.         def __getattr__(self,attrname):  
  7.             print('Trace:'+attrname)  
  8.             self.fetches += 1  
  9.             return getattr(self.wrapped,attrname)  
  10.     return Wrapper  
  11.  
  12. @Tracer  
  13. class Spam:  
  14.     def display(self):  
  15.         print('Spam!'*8)  
  16.  
  17. @Tracer  
  18. class Person:  
  19.     def __init__(self,name,hours,rate):  
  20.         self.name = name  
  21.         self.hours = hours  
  22.         self.rate = rate  
  23.     def pay(self):  
  24.         return self.hours * self.rate  
  25.   
  26. food = Spam()  
  27. food.display()  
  28. print([food.fetches])  
  29.   
  30. bob = Person('Bob',40,50)  
  31. print(bob.name)  
  32. print(bob.pay())  
  33.   
  34. print('')  
  35. sue = Person('Sue',rate=100,hours = 60)  
  36. print(sue.name)  
  37. print(sue.pay())  
  38.   
  39. print(bob.name)  
  40. print(bob.pay())  
  41. print([bob.fetches,sue.fetches])  

通过拦截实例创建调用,这里的类装饰器允许我们跟踪整个对象接口,例如,对其任何属性的访问。

Spam和Person类的实例上的属性获取都会调用Wrapper类中的__getattr__逻辑,由于food和bob确实都是Wrapper的实例,得益于装饰器的实例创建调用重定向,输出如下:

[python] view plain copy
 
  1. Trace:display  
  2. Spam!Spam!Spam!Spam!Spam!Spam!Spam!Spam!  
  3. [1]  
  4. Trace:name  
  5. Bob  
  6. Trace:pay  
  7. 2000  
  8.   
  9. Trace:name  
  10. Sue  
  11. Trace:pay  
  12. 6000  
  13. Trace:name  
  14. Bob  
  15. Trace:pay  
  16. 2000  
  17. [4, 2]  

========================================================================================

 

示例:实现私有属性
如下的类装饰器实现了一个用于类实例属性的Private声明,也就是说,属性存储在一个实例上,或者从其一个类继承而来。不接受从装饰的类的外部对这样的属性的获取和修改访问,但是,仍然允许类自身在其方法中自由地访问那些名称。类似于Java中的private属性。

[python] view plain copy
 
  1. traceMe = False  
  2. def trace(*args):  
  3.     if traceMe:  
  4.         print('['+ ' '.join(map(str,args))+ ']')  
  5.   
  6. def Private(*privates):  
  7.     def onDecorator(aClass):  
  8.         class onInstance:  
  9.             def __init__(self,*args,**kargs):  
  10.                 self.wrapped = aClass(*args,**kargs)  
  11.             def __getattr__(self,attr):  
  12.                 trace('get:',attr)  
  13.                 if attr in privates:  
  14.                     raise TypeError('private attribute fetch:'+attr)  
  15.                 else:  
  16.                     return getattr(self.wrapped,attr)  
  17.             def __setattr__(self,attr,value):  
  18.                 trace('set:',attr,value)  
  19.                 if attr == 'wrapped': # 这里捕捉对wrapped的赋值  
  20.                     self.__dict__[attr] = value  
  21.                 elif attr in privates:  
  22.                     raise TypeError('private attribute change:'+attr)  
  23.                 else: # 这里捕捉对wrapped.attr的赋值  
  24.                     setattr(self.wrapped,attr,value)  
  25.         return onInstance  
  26.     return onDecorator  
  27.   
  28. if __name__ == '__main__':  
  29.     traceMe = True  
  30.  
  31.     @Private('data','size')  
  32.     class Doubler:  
  33.         def __init__(self,label,start):  
  34.             self.label = label  
  35.             self.data = start  
  36.         def size(self):  
  37.             return len(self.data)  
  38.         def double(self):  
  39.             for i in range(self.size()):  
  40.                 self.data[i] = self.data[i] * 2  
  41.         def display(self):  
  42.             print('%s => %s'%(self.label,self.data))  
  43.   
  44.     X = Doubler('X is',[1,2,3])  
  45.     Y = Doubler('Y is',[-10,-20,-30])  
  46.   
  47.     print(X.label)  
  48.     X.display()  
  49.     X.double()  
  50.     X.display()  
  51.   
  52.     print(Y.label)  
  53.     Y.display()  
  54.     Y.double()  
  55.     Y.label = 'Spam'  
  56.     Y.display()  
  57.   
  58.     # 这些访问都会引发异常  
  59.     """  
  60.     print(X.size())  
  61.     print(X.data)  
  62.   
  63.     X.data = [1,1,1]  
  64.     X.size = lambda S:0  
  65.     print(Y.data)  
  66.     print(Y.size())  

这个示例运用了装饰器参数等语法,稍微有些复杂,运行结果如下:

[python] view plain copy
 
    1. [set: wrapped <__main__.Doubler object at 0x03421F10>]  
    2. [set: wrapped <__main__.Doubler object at 0x031B7470>]  
    3. [get: label]  
    4. is  
    5. [get: display]  
    6. is => [1, 2, 3]  
    7. [get: double]  
    8. [get: display]  
    9. is => [2, 4, 6]  
    10. [get: label]  
    11. is  
    12. [get: display]  
    13. is => [-10, -20, -30]  
    14. [get: double]  
    15. [set: label Spam]  
    16. [get: display]  
    17. Spam => [-20, -40, -60]  

 

一, 引用

[书] 流畅的Python

[书] Effective Python


二, 基本概念

2372598012-59a77c7d2a7de_articlex

问题1:装饰器是什么?

解答: 严格来说,装饰器只是语法糖, 装饰器是可调用的对象,可以像常规的可调用对象那样调用,特殊的地方是装饰器的参数是一个函数

问题2:装饰器有什么特性?

解答: 装饰器有2个特性,一是可以把被装饰的函数替换成其他函数, 二是可以在加载模块时候立即执行

问题3:如何使用被装饰函数中的参数?

解答: 通过args 和 *kwargs 传递被修饰函数中的参数

 


三, 叠放装饰器

3981774596-59a44a325eb8d_articlex

问题1:叠放装饰器执行顺序是什么?

解答: 如果一个函数被多个装饰器修饰,其实应该是该函数先被最里面的装饰器修饰后(下面例子中函数main()先被inner装饰,变成新的函数),变成另一个函数后,再次被装饰器修饰

 


四, 标准库中的装饰器

3981774596-59a44a325eb8d_articlex

问题1: 标准库中都有哪些装饰器?

解答: 标准库中有多种装饰器, 例如:装饰方法的函数有property, classmethod, staticmethod; functools模块中的lru_cache, singledispatch,  wraps 等等

问题2:为什么要使用@wraps装饰器?它的作用是什么?

解答: 使用装饰器会产生我们可能不希望出现的副作用, 例如:改变被修饰函数名称,对于调试器或者对象序列化器等需要使用内省机制的那些工具,可能会无法正常运行;其实调用装饰器后,会将同一个作用域中原来函数同名的那个变量(例如下面的func_1),重新赋值为装饰器返回的对象;使用@wraps后,会把与内部函数(被修饰函数,例如下面的func_1)相关的重要元数据全部复制到外围函数(例如下面的decorate_inner)

 


五, 装饰器设计模式

3981774596-59a44a325eb8d_articlex

问题1: 什么是装饰器设计模式?

解答: 动态的给一个对象添加一些额外的职责,就扩展功能而言,装饰器模式比子类化更加灵活,在设计模式中,装饰器和组件都是抽象类,为了给具体的组件添加行为,具体的装饰器实例要包装具体组件的实例,即,装饰器和所装饰的组件接口一致,对使用该组建的客户透明,将客户的请求转发给该组件,并且可能在转发前后执行一些额外的操作,透明性使得可以递归嵌套多个装饰器,从而可以添加任意多个功能

问题2: Python中的装饰器函数和设计模式中的装饰器模式有什么关系?

解答:  修饰器模式和Python修饰器之间并不是一对一的等价关系, Python装饰器函数更为强大,不仅仅可以实现装饰器模式。

 

 

 
posted @ 2018-01-07 17:53  alxe_yu  阅读(114)  评论(0)    收藏  举报