Python——编写类装饰器

发布时间:2019-09-25 08:21:37编辑:auto阅读(1504)

    编写类装饰器

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

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

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

    instances = {} # 全局变量,管理实例
    def getInstance(aClass, *args):
        if aClass not in instances:
            instances[aClass] = aClass(*args)
        return instances[aClass]     #每一个类只能存在一个实例
    
    def singleton(aClass):
        def onCall(*args):
            return getInstance(aClass,*args)
        return onCall
    为了使用它,装饰用来强化单体模型的类:
    @singleton        # Person = singleton(Person)
    class Person:
        def __init__(self,name,hours,rate):
            self.name = name
            self.hours = hours
            self.rate = rate
        def pay(self):
            return self.hours * self.rate
    
    @singleton        # Spam = singleton(Spam)
    class Spam:
        def __init__(self,val):
            self.attr = val
            
    bob = Person('Bob',40,10)
    print(bob.name,bob.pay())
    
    sue = Person('Sue',50,20)
    print(sue.name,sue.pay())
    
    X = Spam(42)
    Y = Spam(99)
    print(X.attr,Y.attr)
    现在,当Person或Spam类稍后用来创建一个实例的时候,装饰器提供的包装逻辑层把实例构建调用指向了onCall,它反过来调用getInstance,以针对每个类管理并分享一个单个实例,而不管进行了多少次构建调用。
    程序输出如下:
    Bob 400
    Bob 400
    42 42
    在这里,我们使用全局的字典instances来保存实例,还有一个更好的解决方案就是使用Python3中的nonlocal关键字,它可以为每个类提供一个封闭的作用域,如下:
    def singleton(aClass):
    	instance = None
    	def onCall(*args):
    		nonlocal instance
    		if instance == None:
    			instance = aClass(*args)
    		return instance
    	return onCall
    当然,我们也可以用类来编写这个装饰器——如下代码对每个类使用一个实例,而不是使用一个封闭作用域或全局表:
    class singleton:
    	def __init__(self,aClass):
    		self.aClass = aClass
    		self.instance = None
    	def __call__(self,*args):
    		if self.instance == None:
    			self.instance = self.aClass(*args)
    		return self.instance
    -------------------------------------------------------------------------------------------------------------------------------------

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

    class Wrapper:
    	def __init__(self,obj):
    		self.wrapped = obj
    	def __getattr__(self,attrname):
    		print('Trace:',attrname)
    		return getattr(self.wrapped,attrname)
    
    	
    >>> x = Wrapper([1,2,3])
    >>> x.append(4)
    Trace: append
    >>> x.wrapped
    [1, 2, 3, 4]
    >>>
    >>> x = Wrapper({'a':1,'b':2})
    >>> list(x.keys())
    Trace: keys
    ['b', 'a']
    在这段代码中,Wrapper类拦截了对任何包装对象的属性的访问,打印出一条跟踪信息,并且使用内置函数getattr来终止对包装对象的请求。

    类装饰器为编写这种__getattr__技术来包装一个完整接口提供了一个替代的、方便的方法。如下:
    def Tracer(aClass):
        class Wrapper:
            def __init__(self,*args,**kargs):
                self.fetches = 0
                self.wrapped = aClass(*args,**kargs)
            def __getattr__(self,attrname):
                print('Trace:'+attrname)
                self.fetches += 1
                return getattr(self.wrapped,attrname)
        return Wrapper
    
    @Tracer
    class Spam:
        def display(self):
            print('Spam!'*8)
    
    @Tracer
    class Person:
        def __init__(self,name,hours,rate):
            self.name = name
            self.hours = hours
            self.rate = rate
        def pay(self):
            return self.hours * self.rate
    
    food = Spam()
    food.display()
    print([food.fetches])
    
    bob = Person('Bob',40,50)
    print(bob.name)
    print(bob.pay())
    
    print('')
    sue = Person('Sue',rate=100,hours = 60)
    print(sue.name)
    print(sue.pay())
    
    print(bob.name)
    print(bob.pay())
    print([bob.fetches,sue.fetches])
    通过拦截实例创建调用,这里的类装饰器允许我们跟踪整个对象接口,例如,对其任何属性的访问。

    Spam和Person类的实例上的属性获取都会调用Wrapper类中的__getattr__逻辑,由于food和bob确实都是Wrapper的实例,得益于装饰器的实例创建调用重定向,输出如下:
    Trace:display
    Spam!Spam!Spam!Spam!Spam!Spam!Spam!Spam!
    [1]
    Trace:name
    Bob
    Trace:pay
    2000
    
    Trace:name
    Sue
    Trace:pay
    6000
    Trace:name
    Bob
    Trace:pay
    2000
    [4, 2]
    ========================================================================================

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

    traceMe = False
    def trace(*args):
        if traceMe:
            print('['+ ' '.join(map(str,args))+ ']')
    
    def Private(*privates):
        def onDecorator(aClass):
            class onInstance:
                def __init__(self,*args,**kargs):
                    self.wrapped = aClass(*args,**kargs)
                def __getattr__(self,attr):
                    trace('get:',attr)
                    if attr in privates:
                        raise TypeError('private attribute fetch:'+attr)
                    else:
                        return getattr(self.wrapped,attr)
                def __setattr__(self,attr,value):
                    trace('set:',attr,value)
                    if attr == 'wrapped': # 这里捕捉对wrapped的赋值
                        self.__dict__[attr] = value
                    elif attr in privates:
                        raise TypeError('private attribute change:'+attr)
                    else: # 这里捕捉对wrapped.attr的赋值
                        setattr(self.wrapped,attr,value)
            return onInstance
        return onDecorator
    
    if __name__ == '__main__':
        traceMe = True
    
        @Private('data','size')
        class Doubler:
            def __init__(self,label,start):
                self.label = label
                self.data = start
            def size(self):
                return len(self.data)
            def double(self):
                for i in range(self.size()):
                    self.data[i] = self.data[i] * 2
            def display(self):
                print('%s => %s'%(self.label,self.data))
    
        X = Doubler('X is',[1,2,3])
        Y = Doubler('Y is',[-10,-20,-30])
    
        print(X.label)
        X.display()
        X.double()
        X.display()
    
        print(Y.label)
        Y.display()
        Y.double()
        Y.label = 'Spam'
        Y.display()
    
        # 这些访问都会引发异常
        """
        print(X.size())
        print(X.data)
    
        X.data = [1,1,1]
        X.size = lambda S:0
        print(Y.data)
        print(Y.size())
    这个示例运用了装饰器参数等语法,稍微有些复杂,运行结果如下:
    [set: wrapped <__main__.Doubler object at 0x03421F10>]
    [set: wrapped <__main__.Doubler object at 0x031B7470>]
    [get: label]
    X is
    [get: display]
    X is => [1, 2, 3]
    [get: double]
    [get: display]
    X is => [2, 4, 6]
    [get: label]
    Y is
    [get: display]
    Y is => [-10, -20, -30]
    [get: double]
    [set: label Spam]
    [get: display]
    Spam => [-20, -40, -60]

关键字