Python——編寫類裝飾器
- 2020 年 1 月 6 日
- 筆記
編寫類裝飾器
類裝飾器類似於函數裝飾器的概念,但它應用於類,它們可以用於管理類自身,或者用來攔截實例創建調用以管理實例。
————————————————————————————————————————————-
單體類 由於類裝飾器可以攔截實例創建調用,所以它們可以用來管理一個類的所有實例,或者擴展這些實例的接口。 下面的類裝飾器實現了傳統的單體編碼模式,即最多只有一個類的一個實例存在。
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]