正文
__getattr__函数的作用: 如果属性查找(attribute lookup)在实例以及对应的类中(通过__dict__)失败, 那么会调用到类的__getattr__函数, 如果没有定义这个函数,那么抛出AttributeError异常。由此可见,__getattr__一定是作用于属性查找的最后一步,兜底。
我们来看几个例子:
第一个例子,很简单但经典,可以像访问属性一样访问dict中的键值对。
class ObjectDict(dict):
def __init__(self, *args, **kwargs):
super(ObjectDict, self).__init__(*args, **kwargs)
def __getattr__(self, name):
value = self[name]
if isinstance(value, dict):
value = ObjectDict(value)
return value
if __name__ == '__main__':
od = ObjectDict(asf={'a': 1}, d=True)
print od.asf
print od.asf.a
print od.d
image.png
第二个例子,对象属性的lazy initialize。
class WidgetShowLazyLoad(object):
def fetch_complex_attr(self, attrname):
'''可能是比较耗时的操作, 比如从文件读取'''
return attrname
def __getattr__(self, name):
if name not in self.__dict__:
self.__dict__[name] = self.fetch_complex_attr(name)
return self.__dict__[name]
if __name__ == '__main__':
w = WidgetShowLazyLoad()
print 'before', w.__dict__
a=w.lazy_loaded_attr
print 'after', w.__dict__
image.png
可以看到,属性访问前对象中的__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')
Widget.complex_attr_may_not_need
print Widget.__dict__.get('complex_attr_may_not_need')
image.png
第三个例子,我觉的是最实用的,__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):
print 'name',name
return getattr(self.adaptee, name)
if __name__ == '__main__':
a = adapter()
a.foo()
a.bar()
image.png
如果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()
image.png