Python按需生成类的属性

​ 当我们要用一个类来存储数据库中的一条信息,但是我们不知道这条信息中都有什么数据,这时候我们可以利用按需生成属性的方式来保存未知的信息。

__getattr__

​ 按需生成属性可以通过__getattr__ 特殊方法来实现。如果某个类定义了__getattr__ 方法,同时系统在该类对象的实例字典中有差找不到待查询的属性,那么系统就会调用这个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class LazyDB():
def __init__(self):
self.exist = 5

# 此函数会在访问不存在的属性时自动调用
def __getattr__(self, name):
value = 'value for %s' %name
setattr(self, name, value) # 给实例添加此属性和值
return value

db = LazyDB()
print('Before', db.__dict__)
print(db.foo)
print('After:', db.__dict__)

执行结果:

1
2
3
Before {'exist': 5}
value for foo
After: {'exist': 5, 'foo': 'value for foo'}

代码中访问了实例的没有的 foo 属性,这会使解释器自动调用上面定义的__getattr__ 方法,里面的setattr函数给实例添加了foo属性。

__getattribuate__

__getattribuate__ 方法在访问类的属性的时候会自动调用。

__setattr__

__setattr__ 函数无论是在类中给属性赋值,还是在实例中给属性赋值,都会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class LazyDB():
def __init__(self):
self.exist = 5
self.exist2 = 10

# 此函数会在给属性赋值时被调用
def __setattr__(self, key, value):
print('add attr:%s' %key)
super().__setattr__(key,value)


t = LazyDB()
t.foo = 8
print(t.foo)
print(t.__dict__)

执行结果:

1
2
3
4
5
add attr:exist
add attr:exist2
add attr:foo
8
{'exist': 5, 'exist2': 10, 'foo': 8}

我们重写这些内置方法时,可以自行在里面添加一些东西,比如提示什么的。


要注意的是,重写__getattribuate__ 方法和__setattr__ 方法时,需要通过 super() 来完成,否则会陷入无限递归。比如:

1
2
3
4
5
6
7
8
9
10
class BrokenDictionaryDB(object):
def __init__(self, data):
self._data = data

def __getattribute__(self, name):
print('Called __getattribute__(%s)' % name)
return self.name

db = BrokenDictionaryDB(5)
print(db._data)

上面这个代码就会陷入无限递归,因为return self.name 又会调用__getattribute__ 方法。

通过super()来完成就可以防止此问题:

1
2
3
4
5
6
7
8
9
10
11
class BrokenDictionaryDB(object):
def __init__(self, data):
self._data = data

def __getattribute__(self, name):
print('Called __getattribute__(%s)' % name)
# 修改了下面这行代码
return super().__getattribute__(name)

db = BrokenDictionaryDB(5)
print(db._data)