在
之前的文章有提到__getattr__函數的作用: 如果屬性查找(attribute lookup)在實例以及對應的類中(通過__dict__)失敗, 那么會調用到類的__getattr__函數, 如果沒有定義這個函數,那么拋出AttributeError異常。由此可見,__getattr__一定是作用於屬性查找的最后一步,兜底。
我們來看幾個例子:
第一個例子,很簡單但經典,可以像訪問屬性一樣訪問dict中的鍵值對。
1 class ObjectDict(dict): 2 def __init__(self, *args, **kwargs): 3 super(ObjectDict, self).__init__(*args, **kwargs) 4 5 def __getattr__(self, name): 6 value = self[name] 7 if isinstance(value, dict): 8 value = ObjectDict(value) 9 return value 10 11 if __name__ == '__main__': 12 od = ObjectDict(asf={'a': 1}, d=True) 13 print od.asf, od.asf.a # {'a': 1} 1 14 print od.d # True
第二個例子,對象屬性的lazy initialize。
1 class WidgetShowLazyLoad(object): 2 def fetch_complex_attr(self, attrname): 3 '''可能是比較耗時的操作, 比如從文件讀取''' 4 return attrname 5 6 def __getattr__(self, name): 7 if name not in self.__dict__: 8 self.__dict__[name] = self.fetch_complex_attr(name) 9 return self.__dict__[name] 10 11 if __name__ == '__main__': 12 w = WidgetShowLazyLoad() 13 print 'before', w.__dict__ 14 w.lazy_loaded_attr 15 print 'after', w.__dict__
輸出:
before {}
after {'lazy_loaded_attr': 'lazy_loaded_attr'}
可以看到,屬性訪問前對象中的__dict__沒有任何元素,訪問之后就有添加。
這個例子是類實例的屬性的惰性初始化,bottle里面也有一個用descriptor實現類屬性的惰性初始化。
import functools class lazy_attribute(object): """ A property that caches itself to the class object. """ def __init__(self, func): functools.update_wrapper(self, func, updated=[]) self.getter = func def __get__(self, obj, cls): value = self.getter(cls) setattr(cls, self.__name__, value) return value class Widget(object): @lazy_attribute def complex_attr_may_not_need(clz): print 'complex_attr_may_not_need is needed now' return sum(i*i for i in range(1000)) if __name__ == '__main__': print Widget.__dict__.get('complex_attr_may_not_need') # <__main__.lazy_attribute object at 0x02B12450> Widget.complex_attr_may_not_need # complex_attr_may_not_need is needed now print Widget.__dict__.get('complex_attr_may_not_need') # 332833500
第三個例子,我覺的是最實用的,__getattr__使得實現adapter wrapper模式非常容易,我們都知道“組合優於繼承”,__getattr__實現的adapter就是以組合的形式。
class adaptee(object): def foo(self): print 'foo in adaptee' def bar(self): print 'bar in adaptee' class adapter(object): def __init__(self): self.adaptee = adaptee() def foo(self): print 'foo in adapter' self.adaptee.foo() def __getattr__(self, name): return getattr(self.adaptee, name) if __name__ == '__main__': a = adapter() a.foo() a.bar()
如果adapter需要修改adaptee的行為,那么定義一個同名的屬性就行了,其他的想直接“繼承”的屬性,通通交給__getattr__就行了
最后一個例子,是筆者在工作中實際用到__getattr__的例子。本質上和第三個例子差不多
class AlgoImpA(object): def __init__(self): self.obj_attr = 'obj_attr in AlgoImpA' def foo(self): print 'foo in AlgoImpA' def bar(self): print 'bar in AlgoImpA' class AlgoImpB(object): def __init__(self): self.obj_attr = 'obj_attr in AlgoImpB' def foo(self): print 'foo in AlgoImpB' def bar(self): print 'bar in AlgoImpB' class Algo(object): def __init__(self): self.imp_a = AlgoImpA() self.imp_b = AlgoImpB() self.cur_imp = self.imp_a def switch_imp(self): if self.cur_imp == self.imp_a: self.cur_imp = self.imp_b else: self.cur_imp = self.imp_a def __str__(self): return 'Algo with imp %s' % str(self.cur_imp) def __getattr__(self, name): return getattr(self.cur_imp, name) if __name__ == '__main__': algo = Algo() print algo print algo.obj_attr algo.foo() algo.switch_imp() print algo print algo.obj_attr algo.bar()
輸出:
Algo with imp <__main__.AlgoImpA object at 0x02AA2270>
obj_attr in AlgoImpA
foo in AlgoImpA
Algo with imp <__main__.AlgoImpB object at 0x02AA22B0>
obj_attr in AlgoImpB
bar in AlgoImpB
首先,Algo提供給使用者的接口應該盡量簡單,因此應該使用algo.func, 而不是algo.cur_imp.func。其次,AlgoImpA和AlgoImpB都有很多的屬性(泛指函數和數據屬性),使用__getattr__能大幅簡化代碼。Why we use python,life is short。
references: