Python——編寫類裝飾器

編寫類裝飾器

類裝飾器類似於函數裝飾器的概念,但它應用於類,它們可以用於管理類自身,或者用來攔截實例創建調用以管理實例。

————————————————————————————————————————————-

單體類 由於類裝飾器可以攔截實例創建調用,所以它們可以用來管理一個類的所有實例,或者擴展這些實例的接口。 下面的類裝飾器實現了傳統的單體編碼模式,即最多只有一個類的一個實例存在。

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]