无法在“对象”类的实例上设置属性


87

因此,我在回答这个问题的同时玩弄Python ,发现这是无效的:

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

由于AttributeError: 'object' object has no attribute 'attr'。但是,对于从对象继承的任何类,它都是有效的:

class Sub(object):
    pass

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

打印s.attr将按预期显示“ hello”。为什么会这样呢?在Python语言规范中,有什么规定不能将属性分配给香草对象?


纯粹的猜测:object类型是不可变的,是否不能添加新属性?这似乎是最有意义的。
克里斯·卢兹

2
//@S.Lott:请参阅此问题的第一行。纯粹是好奇心。
Smashery

3
您的标题具有误导性,您试图在object类实例上而不是在object类上设置属性。
dhill

Answers:


129

为了支持任意属性分配,对象需要一个__dict__:与对象关联的字典,可以在其中存储任意属性。否则,就无处放置新属性。

的实例object没有随身携带__dict__-如果它这样做了,可怕的循环依赖问题(之前因为dict,像其他所有的事情,从继承object;-),这将鞍每一个在Python对象有一个字典,这将意味着开销当前没有或不需要字典的每个对象中有多少字节(基本上,所有不具有任意可分配属性的对象都没有或不需要字典)。

例如,使用出色的pympler项目(您可以从此处通过svn获取它),我们可以进行一些测量...:

>>> 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,你得注意!(或什至是两个ints,一个self,一个self.foobar-可以重新分配第二个),但肯定比a好得多dint

当类具有__slots__特殊属性(字符串序列),那么class语句(更准确地说,默认元类type)并没有装备该类的每个实例有一个__dict__(有任意属性,因此的能力),只是一个有限,一组具有给定名称的刚性“槽”(基本上是每个可以容纳对某个对象的引用的位置)。

为了换取失去的灵活性,您每个实例将获得大量字节(仅当您有成千上万个实例在四处游荡时才有意义,但是,有一些用例)。


5
这解释了该机制的实现方式,但没有解释为什么以这种方式实现。我可以想到至少两种或三种方法来即时实现添加dict,这不会带来开销方面的缺点,但会增加一些简单性。
ГеоргиКременлиев

请注意,非空__slots__不具有可变长度类型,如工作 strtuple以及在Python 3也int
arekolek '16


这是一个很好的解释,但仍然无法回答为什么(或如何)Sub没有__dict__属性和对象(是Sub继承自)object,如何__module__在继承中添加该属性(以及其他类似属性)?可能这可能是一个新问题
Rodrigo E. Principe

2
对象__dict__仅在第一次使用时才创建,因此内存消耗情况并不像asizeof输出显示的那么简单。(asizeof不知道如何避免__dict__物化。)你可以看到没有得到兑现,直到需要在字典这个例子中,你可以看到负责的代码路径的一个__dict__物化这里
user2357112支持Monica19年

17

正如其他回答者所说的,object“没有” __dict__object所有类型的基类,包括intstr。因此,所提供的任何东西也object将对他们构成负担。即使是简单的可选操作 __dict__,每个值都需要一个额外的指针。对于一个非常有限的实用程序,这将浪费系统中每个对象额外的4-8个字节的内存。


在Python 3.3+中,您可以(并且应该)使用它types.SimpleNamespace来代替虚拟类的实例。


4

这仅仅是由于优化。

字典相对较大。

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

用C定义的大多数(也许所有)类没有优化指令。

如果查看源代码,将会看到有很多检查来检查对象是否具有字典。


1

因此,在研究我自己的问题时,我发现了有关Python语言的问题:您可以继承int之类的东西,并且看到相同的行为:

>>> class MyInt(int):
       pass

>>> x = MyInt()
>>> print x
0
>>> x.hello = 4
>>> print x.hello
4
>>> x = x + 1
>>> print x
1
>>> print x.hello
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
AttributeError: 'int' object has no attribute 'hello'

我认为最后的错误是因为add函数返回一个int,所以__add__为了保留我的自定义属性,我必须重写诸如此类的函数。但是,当我想到“ int”之类的“ object”时,这一切对我来说(我认为)是有意义的。


0

这是因为对象是“类型”,而不是类。通常,在C扩展中定义的所有类(如所有内置数据类型,以及诸如numpy数组之类的东西)都不允许添加任意属性。


但是object()是对象,就像Sub()是对象一样。我的理解是s和o都是对象。那么s和o之间的根本区别是什么?是一个实例化类型,另一个是实例化类吗?
Smashery

答对了。这就是问题所在。
Ryan

1
在Python 3中,类型和类之间的区别实际上并不存在。因此,“类型”和“类”现在是相当的同义词。但是__dict__,由于Alex Martelli给出的原因,您仍然不能向没有的类添加属性。
下午19年


-2

这(IMO)是Python的基本限制之一-您无法重新打开类。我相信实际的问题是由以下事实引起的:用C实现的类不能在运行时进行修改...子类可以,但基类不能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.