python __getattr__ 巧妙應用

本文轉載自:http://www.cnblogs.com/xybaby/p/6280313.html 作者:xybaby 轉載請註明該聲明。
  在 之前的文章有提到__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:
發佈了0 篇原創文章 · 獲贊 130 · 訪問量 77萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章