在
之前的文章有提到__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: