The proper use of __slots__ is to save space in objects. Instead of having
a dynamic dict that allows adding attributes to objects at anytime,
there is a static structure which does not allow additions after creation.
[This use of __slots__ eliminates the overhead of one dict for every object.] While this is sometimes a useful optimization, it would be completely
unnecessary if the Python interpreter was dynamic enough so that it would
only require the dict when there actually were additions to the object.
Unfortunately there is a side effect to slots. They change the behavior of
the objects that have slots in a way that can be abused by control freaks
and static typing weenies. This is bad, because the control freaks should
be abusing the metaclasses and the static typing weenies should be abusing
decorators, since in Python, there should be only one obvious way of doing something.
Making CPython smart enough to handle saving space without __slots__ is a major
undertaking, which is probably why it is not on the list of changes for P3k (yet).
from abc import ABC
class AbstractA(ABC):
__slots__ = ()
class BaseA(AbstractA):
__slots__ = ('a',)
class AbstractB(ABC):
__slots__ = ()
class BaseB(AbstractB):
__slots__ = ('b',)
class Child(AbstractA, AbstractB):
__slots__ = ('a', 'b')
c = Child() # no problem!
将'__dict__'添加到__slots__以获取动态分配:
class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
class Foo(object):
__slots__ = 'foo', 'bar'
class Bar(object):
__slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()
>>> class Baz(Foo, Bar): pass
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
class AbstractBase:
__slots__ = ()
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'
>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>
在Python2.7中:
>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>
在Python3.6中
>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>
所以我会记住这一点,因为这是一个已经解决的问题。
对(截至2016年10月2日)接受答案的评论
第一段是半简短的解释,半预测。这是唯一能回答这个问题的部分
The proper use of __slots__ is to save space in objects. Instead of having a dynamic dict that allows adding attributes to objects at anytime, there is a static structure which does not allow additions after creation. This saves the overhead of one dict for every object that uses slots
下半场是一厢情愿的想法,而且离题了:
While this is sometimes a useful optimization, it would be completely unnecessary if the Python interpreter was dynamic enough so that it would only require the dict when there actually were additions to the object.
If you subclass a built-in type, extra space is automatically added to the instances to accomodate __dict__ and __weakrefs__. (The __dict__ is not initialized until you use it though, so you shouldn't worry about the space occupied by an empty dictionary for each instance you create.) If you don't need this extra space, you can add the phrase "__slots__ = []" to your class.
如果要实例化同一类的许多(成百上千)对象,则需要使用
__slots__
。__slots__
仅作为内存优化工具存在。强烈建议使用
__slots__
来约束属性创建。使用
__slots__
的pickle对象不能使用默认(最旧的)pickle协议;需要指定更高版本。python的一些其他内省特性也可能受到不利影响。
引用Jacob Hallen:
TLDR编号:
特殊属性
__slots__
允许您显式地声明希望对象实例具有哪些实例属性,并显示预期结果:节省的空间来自
__dict__
。__dict__
和__weakref__
创建,如果父类拒绝它们并且您声明__slots__
。快速警告
小小的警告,您应该在继承树中只声明一次特定的时隙。例如:
Python不反对当你弄错了(它可能应该),问题可能不会以其他方式显现,但是你的对象将占用比他们应该占用更多的空间。
最大的警告是多重继承-不能组合多个“带非空槽的父类”。
为了适应这种限制,请遵循最佳实践:去掉所有父类的抽象(除了一个或所有父类之外),这些父类的具体类和新的具体类将共同继承自这些抽象(就像标准库中的抽象基类一样)。
有关示例,请参见下面关于多重继承的部分。
要求:
若要使在
__slots__
中命名的属性实际存储在插槽中而不是__dict__
,则类必须从object
继承。为了防止创建
__dict__
,必须从object
继承,继承中的所有类都必须声明__slots__
,并且任何类都不能有'__dict__'
项。如果你想继续阅读的话,有很多细节。
为什么使用
__slots__
:更快的属性访问Python的创建者Guido van Rossum,states实际上是他为了更快的属性访问而创建的
__slots__
。很容易证明访问速度显著加快:
以及
在Ubuntu上的Python 3.5中,时隙访问快了近30%。
在Windows上的Python2中,我已经测量到它大约快了15%。
为什么使用
__slots__
:节省内存__slots__
的另一个目的是减少每个对象实例占用的内存空间。My own contribution to the documentation clearly states the reasons behind this:
SQLAlchemy attributes为
__slots__
节省了大量内存。为了验证这一点,在Ubuntu Linux上使用Python 2.7的Anaconda发行版,在声明了
guppy.hpy
(又名heapy)和sys.getsizeof
的情况下,没有声明__slots__
的类实例的大小是64字节。这并不包括__dict__
。再次感谢Python的懒惰计算,__dict__
在被引用之前显然不会被调用,但是没有数据的类通常是无用的。当被调用存在时,__dict__
属性至少额外280字节。相比之下,声明为
__slots__
的类实例(没有数据)只有16个字节,插槽中有一个项的类实例总共有56个字节,有两个项的类实例有64个字节。对于64位Python,我以字节为单位说明了Python 2.7和3.6中的内存消耗,对于dict在3.6中增长的每个点(除了0、1和2个属性),分别是
__slots__
和__dict__
(未定义槽):因此,尽管Python 3中有更小的dict,但是我们可以看到实例的
__slots__
伸缩性如何很好地节省我们的内存,这也是您希望使用__slots__
的主要原因。为了完整起见,请注意,在Python 2中,类的名称空间中的每个时隙都有一个一次性的开销,即64个字节,在Python 3中为72个字节,因为时隙使用诸如属性之类的数据描述符,称为“成员”。
演示
__slots__
:若要拒绝创建
__dict__
,必须子类object
:现在:
或子类定义
__slots__
的其他类现在:
但是:
若要允许在子类化时隙对象时创建
__dict__
,只需将'__dict__'
添加到__slots__
(请注意,插槽是有序的,不应重复父类中已存在的插槽):以及
或者您甚至不需要在子类中声明
__slots__
,并且您仍将使用来自父类的时隙,但不限制__dict__
的创建:以及:
但是,
__slots__
可能会导致多重继承问题:因为从同时具有两个非空插槽的父类创建子类失败:
如果遇到此问题,您可以从父级中删除
__slots__
,或者如果您控制了父级,则为它们提供空槽,或者重构为抽象:将
'__dict__'
添加到__slots__
以获取动态分配:现在:
因此,使用插槽中的
'__dict__'
时,我们会失去一些大小优势,因为这样做的好处是具有动态分配,并且仍然为我们所期望的名称提供插槽。当您从一个未开槽的对象继承时,当您使用
__slots__
时会得到相同的语义-位于__slots__
中的名称指向开槽值,而任何其他值都放在实例的__dict__
中。避免
__slots__
因为您希望能够动态添加属性实际上不是一个好的理由-如果需要,只需将"__dict__"
添加到您的__slots__
中。如果您需要这个特性,也可以类似地将
__weakref__
显式地添加到__slots__
。子类化namedtuple时设置为空元组:
namedtuple内置的不可变实例非常轻量级(本质上是元组的大小),但要获得这些好处,如果对它们进行子类化,则需要自己执行:
用法:
尝试分配意外属性会引发一个
AttributeError
,因为我们已经阻止了__dict__
的创建:您可以通过关闭
__slots__ = ()
来允许__dict__
创建,但不能对tuple的子类型使用非空__slots__
。最大的警告:多重继承
即使对于多个父级,非空插槽是相同的,它们也不能一起使用:
在父级中使用空的
__slots__
似乎提供了最大的灵活性,允许子级选择阻止或允许(通过添加'__dict__'
来获取动态赋值,请参阅上面的部分)创建__dict__
:你没有的插槽-所以如果你添加它们,并在以后删除它们,应该不会造成任何问题。
在这里走投无路:如果您正在编写mixins或使用abstract base classes(不打算被实例化),在这些父类中使用空的
__slots__
似乎是子类灵活性方面的最佳方法。为了演示,首先,让我们创建一个类,其中包含要在多重继承下使用的代码
我们可以通过继承和声明预期的插槽来直接使用上述内容:
但我们不在乎这个,这是一个微不足道的单一继承,我们需要另一个我们也可以继承的类,可能有一个嘈杂的属性:
如果两个基地都有空位,我们就不能做下面的事。(事实上,如果我们愿意的话,我们可以给
AbstractBase
非空的插槽a和b,并将它们排除在下面的声明之外—将它们留在中是错误的):现在我们可以通过多重继承来实现这两种功能,并且仍然可以拒绝
__dict__
和__weakref__
实例化:避免开槽的其他情况:
__class__
赋值时,请避免它们(并且您可以不要添加它们),除非插槽布局相同。(我对了解谁在做这件事以及为什么这么做很感兴趣。)您也许可以从
__slots__
documentation (the 3.7 dev docs are the most current)的其余部分中梳理出进一步的注意事项,我最近对这些内容做出了重要贡献。对其他答案的评论
目前最热门的答案引用了过时的信息,在一些重要的方面是相当不得体的。
不要“在实例化大量对象时只使用
__slots__
”我引用:
例如,来自
collections
模块的抽象基类没有被实例化,但是为它们声明了__slots__
。为什么?
如果用户希望拒绝
__dict__
或__weakref__
创建,则这些内容在父类中不能使用。__slots__
有助于在创建接口或混合时重用。确实,许多Python用户不是为了可重用性而编写的,但是如果是这样,可以选择拒绝不必要的空间使用是很有价值的。
__slots__
不会破坏酸洗当挑选一个时隙对象时,您可能会发现它抱怨一个误导性的
TypeError
:这实际上是不正确的。此消息来自最早的协议,这是默认协议。您可以使用
-1
参数选择最新的协议。在Python 2.7中,这是2
(在2.3中引入),在3.6中是4
。在Python2.7中:
在Python3.6中
所以我会记住这一点,因为这是一个已经解决的问题。
对(截至2016年10月2日)接受答案的评论
第一段是半简短的解释,半预测。这是唯一能回答这个问题的部分
下半场是一厢情愿的想法,而且离题了:
Python实际上做了类似的事情,只在访问时创建
__dict__
,但是创建许多没有数据的对象是相当荒谬的。第二段过分简化并忽略了避免
__slots__
的实际原因。下面是不避免槽的真正原因(对于实际原因,请参阅上面我的其余答案):然后讨论用Python实现这个错误目标的其他方法,而不是讨论与
__slots__
有关的任何事情。第三段更是一厢情愿。加在一起,大部分都是离谱的内容,回答者甚至没有作者和贡献的弹药批评者的网站。
内存使用证据
创建一些普通对象和开槽对象:
例举一百万个:
用
guppy.hpy().heap()
检查:访问常规对象及其
__dict__
,然后再次检查:这与Python的历史是一致的,从Unifying types and classes in Python 2.2
相关问题 更多 >
编程相关推荐