如何动态地向类添加属性?

318 投票
26 回答
393003 浏览
提问于 2025-04-15 13:51

目标是创建一个模拟类,它的行为像数据库返回的结果集。

举个例子,如果数据库查询返回了一个字典,比如 {'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 个回答

47

你不需要为这个使用属性。只需重写 __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!
71

目标是创建一个模拟类,它的行为像数据库的结果集。

所以你想要的是一个字典,这样你就可以用 a['b'] 的方式像 a.b 一样来访问数据,对吧?

这很简单:

class atdict(dict):
    __getattr__= dict.__getitem__
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__
458

我想我应该把这个回答扩展一下,现在我年纪大了,懂得也多了,知道了事情的真相。晚做总比不做好。

可以动态地给一个类添加属性。但这里有个关键点:你必须把它添加到上。

>>> 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构造函数的fgetfsetfdel

描述符实际上是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来表达,这点非常酷。 :)

哦,对了,我之前写过一篇关于描述符的长篇博客,如果你感兴趣的话可以去看看。

撰写回答