无法设置"对象"类实例的属性

98 投票
7 回答
17640 浏览
提问于 2025-04-15 14:52

我在用Python玩的时候,顺便回答了这个问题,发现这样写是不对的:

o = object()
o.attr = 'hello'

因为会出现一个错误,叫做AttributeError: 'object' object has no attribute 'attr'。不过,如果你用的是从对象(object)继承的类,那就没问题了:

class Sub(object):
    pass

s = Sub()
s.attr = 'hello'

打印s.attr时,会如预期显示'hello'。为什么会这样呢?Python的语言规范中有什么规定,导致你不能给普通对象添加属性呢?


想了解其他解决方法,可以看看我该如何创建一个对象并给它添加属性?

7 个回答

4

这主要是因为优化的原因。

字典(Dict)相对比较大。

>>> import sys
>>> sys.getsizeof((lambda:1).__dict__)
140

大多数(可能是所有)用C语言定义的类都没有字典,这是为了优化。

如果你查看这个源代码,你会发现有很多检查,用来判断这个对象是否有字典。

18

正如其他回答者所说,object 是所有类型的基础类,包括 int(整数)和 str(字符串)。这意味着 object 提供的任何东西都会对这些类型造成负担。即使是像 可选的 __dict__ 这样的简单功能,每个值也需要额外的指针;这会让系统中的每个对象多占用 4 到 8 字节的内存,但其实用性非常有限。


在 Python 3.3 及以上版本中,不需要创建一个虚假的类实例,你可以(而且应该)使用 types.SimpleNamespace 来实现这个功能。

144

为了支持随意添加属性,一个对象需要有一个叫做 __dict__ 的东西:这是与对象关联的一个字典,可以用来存储各种属性。否则,就没有地方可以“放”新的属性了。

一个 object 的实例并不自带 __dict__——如果有的话,在解决一个可怕的循环依赖问题之前(因为 dict 和大多数其他东西一样,都是从 object 继承来的;-),这会让Python中的每个对象都带上一个字典,这样每个目前不需要字典的对象就会多占用很多字节(基本上,所有没有随意可分配属性的对象都不需要字典)。

举个例子,使用优秀的 pympler 项目(你可以通过 这里 下载),我们可以做一些测量...:

>>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16

你肯定不希望每个 int 占用 144 字节,而不是仅仅 16 字节,对吧?-)

现在,当你创建一个类(从任何东西继承)时,情况就变了...:

>>> class dint(int): pass
... 
>>> asizeof.asizeof(dint(23))
184

...这时 __dict__ 就会被添加进来(还有一点额外的开销)——所以一个 dint 的实例可以有随意的属性,但你为这种灵活性付出了相当的空间代价。

那么,如果你只想要一个 int 加上一个额外的属性 foobar 呢...? 这虽然是个少见的需求,但Python确实提供了一种特殊的机制来实现这个...

>>> class fint(int):
...   __slots__ = 'foobar',
...   def __init__(self, x): self.foobar=x+100
... 
>>> asizeof.asizeof(fint(23))
80

...不过要注意,它并不完全像一个 int 那么小!(或者说两个 int,一个是 self,一个是 self.foobar——第二个可以被重新赋值),但肯定比 dint 要好得多。

当类有一个叫 __slots__ 的特殊属性(一个字符串序列)时,class 语句(更准确地说,默认的元类 type)就不会给该类的每个实例配备 __dict__(因此也就没有随意添加属性的能力),而是只提供一个有限且固定的“槽”(基本上是可以存放某个对象引用的地方),这些槽有特定的名称。

为了失去的灵活性,你每个实例可以节省很多字节(这可能只有在你有成千上万的实例时才有意义,但确实有这样的使用场景)。

撰写回答