基本用法:
1,使用__getattr__实现惰性/按需生成属性
Python中普通的实例属性, @property属性和描述符属性都必须预先定义好。但是有些场景下,例如ORM系统,我们事先并不知道类是否含有属性,需要根据需求动态生成属性。这种场景下,我们可以通过Python的__getattr__来实现动态按需生成属性。
如果类中定义了__getattr__,同时系统在类中对该类实例的__dict__中找不到待查询的属性,就会自动调用__getattr__方法。
举例如下:
class LazyDB(object):
def __init__(self):
self.exist = 1
def __getattr__(self, item):
print('__getattr__ is called')
value = ' '.join(['default value: ', item])
setattr(self, item, value)
return value
data = LazyDB()
print("Before: ", data.__dict__)
print(data.foo) ##类LazyDB并没有预先定义属性foo,此处动态生成了属性foo
print("After: ", data.__dict__)
print(data.foo) ##此时类LazyDB已经有属性foo,直接返回了foo,不会再次调用__getattr__
###输出如下:
Before: {'exist': 1}
__getattr__ is called
default value: foo
After: {'exist': 1, 'foo': 'default value: foo'}
default value: foo
2,使用__getattribute__对属性的访问做额外处理
假设我们需要在数据库中实现事物(transaction)处理,即每次在访问属性时,需要额外调用特殊方法检查数据库中对应的行是否有效,以及相关的事务是否依然开放。此时使用__getattr__无法实现这种功能,因为第二次访问属性时,Python会直接返回上首次调用时存储在__dict__中的属性值,而不会再次调用__getattr__插寻属性的状态。此种情况下我们需要使用__getattribute__,该方法在用户每次访问属性是都会被调用。
class LazyDB(object):
def __init__(self):
self.exist = 1
def __getattribute__(self, item):
print('__getattribute__ (%s) called' % item)
try:
return super().__getattribute__(item)
except AttributeError:
value = ' '.join(['default value: ', item])
setattr(self, item, value)
return value
data = LazyDB()
print(data.foo) ##每次访问类属性时都会被调用,此处是第1次调用
print(data.foo) ##每次访问类属性时都会被调用,此处是第2次调用
print(data.__dict__) ##每次访问类属性时都会被调用,此处是第3次待用
###输出如下:
__getattribute__ (foo) called
default value: foo
__getattribute__ (foo) called
default value: foo
__getattribute__ (__dict__) called
{'exist': 1, 'foo': 'default value: foo'}
3,使用__setattr__给属性赋值时,对属性赋值操作做一些额外的处理
如果类中定义了__setattr__,那么对类对象的每一次赋值,无论是直接赋值还是通过内置函数setattr()函数赋值,都会触发__setattr__方法。
class SavingDB(object):
def __setattr__(self, key, value):
print("Called __setattr__")
super().__setattr__(key, value)
data = SavingDB()
data.foo = 134 ##首次调用
print(data.foo)
data.__setattr__('foo', 'hello') ##第2次调用
print(data.foo)
###输出
Called __setattr__
134
Called __setattr__
hello
4,注意事项
在使用__getattribute__和__setattr__时,因为每次访问属性都会被调用,有可能产生反复递归。例如:在查询对象的属性时,从对象的内部字典中,搜寻与待查询属性的关联值时
class LazyDB(object):
def __init__(self, data):
self._data = data
def __getattribute__(self, item):
print('__getattribute__ (%s) called' % item)
return self._data[item]
data = LazyDB({'foo': 3})
print(data.foo)
##输出:
__getattribute__ (_data) called
__getattribute__ (_data) called
__getattribute__ (_data) called
__getattribute__ (_data) called
__getattribute__ (_data) called
__getattribute__ (_data) called
.......
RecursionError: maximum recursion depth exceeded while calling a Python object
出现反复递归原因在于,__getattribute__里面访问self._data,这意味着会再次调用__getattribute__,然后会继续访问self._data,并无限次循环。解决办法是采用 super().__getattribute__('_data'),从实例的属性字典里面直接获取_data属性值
class LazyDB(object):
def __init__(self, data):
self._data = data
def __getattribute__(self, item):
print('__getattribute__ (%s) called' % item)
dt = super().__getattribute__('_data')
print(dt)
return dt[item]
data = LazyDB({'foo': 3})
print(data.foo)
##输出
__getattribute__ (foo) called
{'foo': 3}
3
项目实践:
1,根据属性赋值,动态生成url
class URLGenerator(object):
def __init__(self, root_url):
self.url = root_url
def __getattr__(self, item):
method = ["get", "post", "put"]
if item.lower() in method:
return self.url
else:
return URLGenerator('{}/{}'.format(self.url, item))
url_gen = URLGenerator("http://XXXXX.com")
url_data = url_gen.show.users.list.get
print(url_data)
##输出
http://XXXXX.com/show/users/list