在之前的文章有提到__getattr__函数的作用: 如果属性查找(attribute lookup)在实例以及对应的类中(通过__dict__)失败, 那么会调用到类的__getattr__函数, 如果没有定义这个函数,那么抛出AttributeError异常。由此可见,__getattr__一定是作用于属性查找的最后一步,兜底。
我们来看几个例子:
第一个例子,很简单但经典,可以像访问属性一样访问dict中的键值对。
1 classObjectDict(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 ifisinstance(value, dict):8 value =ObjectDict(value)9 returnvalue10
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 classWidgetShowLazyLoad(object):2 deffetch_complex_attr(self, attrname):3 '''可能是比较耗时的操作, 比如从文件读取'''
4 returnattrname5
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_attr15 print 'after', w.__dict__
输出:
before {}
after {'lazy_loaded_attr': 'lazy_loaded_attr'}
可以看到,属性访问前对象中的__dict__没有任何元素,访问之后就有添加。
这个例子是类实例的属性的惰性初始化,bottle里面也有一个用descriptor实现类属性的惰性初始化。
importfunctoolsclasslazy_attribute(object):"""A property that caches itself to the class object."""
def __init__(self, func):
functools.update_wrapper(self, func, updated=[])
self.getter=funcdef __get__(self, obj, cls):
value=self.getter(cls)
setattr(cls, self.__name__, value)returnvalueclassWidget(object):
@lazy_attributedefcomplex_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>
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就是以组合的形式。
classadaptee(object):deffoo(self):print 'foo in adaptee'
defbar(self):print 'bar in adaptee'
classadapter(object):def __init__(self):
self.adaptee=adaptee()deffoo(self):print 'foo in adapter'self.adaptee.foo()def __getattr__(self, name):returngetattr(self.adaptee, name)if __name__ == '__main__':
a=adapter()
a.foo()
a.bar()
如果adapter需要修改adaptee的行为,那么定义一个同名的属性就行了,其他的想直接“继承”的属性,通通交给__getattr__就行了
最后一个例子,是笔者在工作中实际用到__getattr__的例子。本质上和第三个例子差不多
classAlgoImpA(object):def __init__(self):
self.obj_attr= 'obj_attr in AlgoImpA'
deffoo(self):print 'foo in AlgoImpA'
defbar(self):print 'bar in AlgoImpA'
classAlgoImpB(object):def __init__(self):
self.obj_attr= 'obj_attr in AlgoImpB'
deffoo(self):print 'foo in AlgoImpB'
defbar(self):print 'bar in AlgoImpB'
classAlgo(object):def __init__(self):
self.imp_a=AlgoImpA()
self.imp_b=AlgoImpB()
self.cur_imp=self.imp_adefswitch_imp(self):if self.cur_imp ==self.imp_a:
self.cur_imp=self.imp_belse:
self.cur_imp=self.imp_adef __str__(self):return 'Algo with imp %s' %str(self.cur_imp)def __getattr__(self, name):returngetattr(self.cur_imp, name)if __name__ == '__main__':
algo=Algo()printalgoprintalgo.obj_attr
algo.foo()
algo.switch_imp()printalgoprintalgo.obj_attr
algo.bar()
输出:
Algo with imp <__main__.algoimpa object at>
obj_attr in AlgoImpA
foo in AlgoImpA
Algo with imp <__main__.algoimpb object at>
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: