所以,我在回答this question时正在玩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语言规范中的内容指定您不能将属性分配给vanilla对象?
答案 0 :(得分:116)
为了支持任意属性赋值,对象需要__dict__
:与对象关联的dict,其中可以存储任意属性。否则,放置新属性无处可去。
object
的一个实例不带有__dict__
- 如果有的话 - 在可怕的循环依赖问题之前(自dict
以来)其他一切,都继承自object
;-),这会使用dict在Python中使用每个对象,这意味着每个对象的多个字节的开销目前没有或不需要dict(基本上,所有没有任意赋值属性的对象都没有或不需要dict)。
例如,使用优秀的pympler
项目(您可以通过here的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
一样小,请注意! (甚至两个int
,一个self
和一个self.foobar
- 第二个可以重新分配),但肯定比dint
好得多。< / p>
当类具有__slots__
特殊属性(字符串序列)时,class
语句(更确切地说,默认元类,type
)会不< / strong>为该类的每个实例配备一个__dict__
(因此具有任意属性的能力),只是一组有限的,刚性的“槽”(基本上每个都可以容纳一个对象的一个引用)用给定的名字。
为了换取失去的灵活性,你可以获得每个实例的大量字节(只有当你有数以万计的实例加入时才有意义,但是用例)。
答案 1 :(得分:16)
正如其他回答者所说,object
没有__dict__
。 object
是所有类型的基类,包括int
或str
。因此,object
提供的任何内容也将成为他们的负担。即使像可选 __dict__
这样简单的事情也需要为每个值添加额外的指针;这会为系统中的每个对象浪费额外的4-8个字节的内存,这是非常有限的实用程序。
在Python 3.3+中,您可以(而且应该)使用types.SimpleNamespace
来代替虚拟类的实例。
答案 2 :(得分:4)
这仅仅是因为优化。
Dicts相对较大。
>>> import sys
>>> sys.getsizeof((lambda:1).__dict__)
140
在C中定义的大多数(可能是所有)类都没有用于优化的字典。
如果查看source code,您会看到有很多检查,看看对象是否有字典。
答案 3 :(得分: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”这样的“对象”时,这一切现在对我(我认为)都有意义。
答案 4 :(得分:0)
这是因为对象是“类型”,而不是类。通常,在C扩展中定义的所有类(如所有内置数据类型和numpy数组之类的东西)都不允许添加任意属性。
答案 5 :(得分:0)
答案 6 :(得分:-1)
这是(IMO)Python的基本限制之一 - 您无法重新打开类。我认为实际的问题是由于在C中实现的类不能在运行时修改的事实...子类可以,但不是基类。