如何动态地向类添加属性?
目标是创建一个模拟类,它的行为像数据库返回的结果集。
举个例子,如果数据库查询返回了一个字典,比如 {'ab':100, 'cd':200}
,那么我希望能看到:
>>> dummy.ab
100
一开始我想,也许可以这样做:
ks = ['ab', 'cd']
vs = [12, 34]
class C(dict):
def __init__(self, ks, vs):
for i, k in enumerate(ks):
self[k] = vs[i]
setattr(self, k, property(lambda x: vs[i], self.fn_readyonly))
def fn_readonly(self, v)
raise "It is ready only"
if __name__ == "__main__":
c = C(ks, vs)
print c.ab
但是 c.ab
返回的是一个属性对象,而不是我想要的结果。
把 setattr
这一行换成 k = property(lambda x: vs[i])
也完全没用。
那么,正确的方法是什么呢,才能在运行时创建一个实例属性呢?
附注:我知道有一个替代方案,可以在 如何使用 __getattribute__
方法? 中找到。
26 个回答
你不需要为这个使用属性。只需重写 __setattr__
方法,就可以让它们变成只读的。
class C(object):
def __init__(self, keys, values):
for (key, value) in zip(keys, values):
self.__dict__[key] = value
def __setattr__(self, name, value):
raise Exception("It is read only!")
好了。
>>> c = C('abc', [1,2,3])
>>> c.a
1
>>> c.b
2
>>> c.c
3
>>> c.d
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'd'
>>> c.d = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
Exception: It is read only!
>>> c.a = 'blah'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
Exception: It is read only!
目标是创建一个模拟类,它的行为像数据库的结果集。
所以你想要的是一个字典,这样你就可以用 a['b'] 的方式像 a.b 一样来访问数据,对吧?
这很简单:
class atdict(dict):
__getattr__= dict.__getitem__
__setattr__= dict.__setitem__
__delattr__= dict.__delitem__
我想我应该把这个回答扩展一下,现在我年纪大了,懂得也多了,知道了事情的真相。晚做总比不做好。
你可以动态地给一个类添加属性。但这里有个关键点:你必须把它添加到类上。
>>> class Foo(object):
... pass
...
>>> foo = Foo()
>>> foo.a = 3
>>> Foo.b = property(lambda self: self.a + 1)
>>> foo.b
4
一个property
其实是一个叫做描述符的简单实现。它是一个对象,专门用来处理某个类的特定属性。可以把它看作是把一个复杂的if
树简化出来的一种方式。
当我在上面的例子中请求foo.b
时,Python会看到类中定义的b
实现了描述符协议——这意味着它是一个有__get__
、__set__
或__delete__
方法的对象。这个描述符负责处理这个属性,所以Python会调用Foo.b.__get__(foo, Foo)
,然后把返回的值作为属性的值返回给你。在property
的情况下,这些方法只是调用你传给property
构造函数的fget
、fset
或fdel
。
描述符实际上是Python用来展示其面向对象实现内部机制的一种方式。实际上,还有一种比property
更常见的描述符。
>>> class Foo(object):
... def bar(self):
... pass
...
>>> Foo().bar
<bound method Foo.bar of <__main__.Foo object at 0x7f2a439d5dd0>>
>>> Foo().bar.__get__
<method-wrapper '__get__' of instancemethod object at 0x7f2a43a8a5a0>
普通的方法就是另一种描述符。它的__get__
方法会把调用的实例作为第一个参数;实际上,它是这样做的:
def __get__(self, instance, owner):
return functools.partial(self.function, instance)
总之,我怀疑这就是为什么描述符只能在类上工作的原因:它们是类本身的基础机制的正式化。它们甚至是这个规则的例外:你显然可以把描述符分配给一个类,而类本身就是type
的实例!实际上,尝试读取Foo.bar
仍然会调用property.__get__
;只是描述符在作为类属性访问时,通常会返回它们自己。
我觉得几乎所有Python的面向对象系统都可以用Python来表达,这点非常酷。 :)
哦,对了,我之前写过一篇关于描述符的长篇博客,如果你感兴趣的话可以去看看。