所以,我在回答这个问题的时候和python一起玩,我发现这是无效的:
1 2
| o = object()
o.attr = 'hello' |
由于AttributeError: 'object' object has no attribute 'attr'。但是,对于从对象继承的任何类,它都是有效的:
1 2 3 4 5
| class Sub(object):
pass
s = Sub()
s.attr = 'hello' |
打印s.attr会按预期显示"你好"。为什么会这样?在Python语言规范中,什么指定了您不能将属性分配给普通对象?
- 纯猜测:object类型是不变的,不能添加新属性?这似乎是最有意义的。
- @洛特:看这个问题的第一行。纯粹的好奇心。
- 您的标题有误导性,您试图在object类实例上设置属性,而不是在object类上设置属性。
为了支持任意属性分配,一个对象需要一个__dict__:一个与该对象相关联的dict,其中可以存储任意属性。否则,就没有地方可以放置新属性。
object的一个实例不携带__dict__--如果它是这样的话,在可怕的循环依赖问题(因为dict和大多数其他东西一样继承了object--之前),这将使python中的每个对象都有一个dict,这意味着每个对象的开销为许多字节,而当前没有或需要一个dict(本质上,所有没有任意可分配属性的对象都没有或需要dict)。
例如,使用优秀的pympler项目(您可以从这里通过SVN获得),我们可以做一些测量…:
1 2 3 4 5
| >>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16 |
你不会希望每个int占用144字节而不是16字节,对吗?-)
现在,当您创建一个类(从任何类继承)时,事情会发生变化…
1 2 3 4
| >>> class dint(int): pass
...
>>> asizeof.asizeof(dint(23))
184 |
…现在增加了__dict__(再加上一点开销)--所以dint实例可以具有任意属性,但是您为此灵活性付出了相当大的空间成本。
那么,如果您只需要一个额外属性foobar的ints,该怎么办?这是一种罕见的需求,但python确实为此提供了一种特殊的机制…
1 2 3 4 5 6
| >>> class fint(int):
... __slots__ = 'foobar',
... def __init__(self, x): self.foobar=x+100
...
>>> asizeof.asizeof(fint(23))
80 |
…还不如江户的那么小,当心!(甚至是两个int,一个是self,一个是self.foobar,第二个可以重新分配),但肯定比dint要好得多。
当类具有__slots__特殊属性(字符串序列)时,那么class语句(更准确地说,默认元类type并没有为该类的每个实例配备__dict__(因此具有任意属性的能力),只是一组有限的、刚性的"slots"(基本上放置在它们都可以用给定的名称保存对某个对象的一个引用。
作为失去灵活性的交换,每个实例可以获得大量的字节(可能只有当您有无数的实例四处游荡时才有意义,但是,有这样的用例)。
- 这解释了该机制是如何实现的,但没有解释为什么它是以这种方式实现的。我可以考虑至少两到三种方法来实现动态添加dict,这不会带来开销方面的负面影响,但会增加一些简单性。
- 注意,非空的__slots__不适用于长度可变的类型,如str、tuple,在python 3中也适用于int。
- 很好,谢谢!如果一个对象没有__dict__属性,它的类必须有__slot__属性吗?
- 这是一个很好的解释,但仍然没有回答为什么(或如何)Sub具有__dict__属性,而object不具有,因为Sub继承自object属性,那么该属性(以及其他类似__module__的属性)在继承中是如何添加的?可能这是个新问题
- 对象的__dict__只在第一次需要时创建,因此内存成本情况并不像asizeof输出显示的那样简单。(asizeof不知道如何避免__dict__物化。)在本例中,您可以看到直到需要时该指令才被物化,并且您可以在这里看到负责__dict__物化的代码路径之一。
正如其他回答者所说,object没有__dict__。object是所有类型的基类,包括int或str。因此,object所提供的一切也将成为他们的负担。即使是像可选的__dict__这样简单的东西,每个值都需要一个额外的指针;对于非常有限的实用程序,这会为系统中的每个对象浪费额外的4-8字节内存。
在python 3.3+中,您可以(并且应该)使用types.SimpleNamespace来代替执行虚拟类的实例。
这仅仅是由于优化。
口述比较大。
1 2 3
| >>> import sys
>>> sys.getsizeof((lambda:1).__dict__)
140 |
在C中定义的大多数(可能全部)类没有用于优化的dict。
如果您查看源代码,您将看到有许多检查来查看对象是否有dict。
因此,在研究我自己的问题时,我发现了关于Python语言的这一点:您可以从int之类的东西继承,并且可以看到相同的行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| >>> 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"时,这一切对我(我想)都是有意义的。
这是因为对象是"类型",而不是类。一般来说,在C扩展中定义的所有类(如所有内置数据类型和numpy数组等)都不允许添加任意属性。
- 但是object()是一个对象,就像sub()是一个对象一样。我的理解是S和O都是对象。那么S和O之间的根本区别是什么呢?一个是实例化类型,另一个是实例化类?
- 答对了。这就是问题所在。
这是(imo)Python的一个基本限制——不能重新打开类。不过,我认为实际的问题是由于在C中实现的类在运行时不能被修改…子类可以,但不能是基类。