In Python, what is the purpose of
__slots__
and what are the cases one should avoid this? 在Python中,__slots__
的目的是什么?在什么情况下应避免这种情况?
The special attribute __slots__
allows you to explicitly state which instance attributes you expect your object instances to have, with the expected results: 特殊属性__slots__
允许您显式说明您希望对象实例具有哪些实例属性,并具有预期的结果:
The space savings is from 节省的空间来自
__dict__
. 将值引用存储在插槽中而不是__dict__
。 __dict__
and __weakref__
creation if parent classes deny them and you declare __slots__
. 如果父类拒绝__dict__
和__weakref__
而您声明__slots__
拒绝创建。 Small caveat, you should only declare a particular slot one time in an inheritance tree. 请注意,您只应在继承树中一次声明一个特定的插槽。 For example: 例如:
class Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
Python doesn't object when you get this wrong (it probably should), problems might not otherwise manifest, but your objects will take up more space than they otherwise should. 遇到错误时,Python不会反对(它应该会),否则问题可能不会显现出来,但是您的对象将比原先占用更多的空间。
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(64, 80)
The biggest caveat is for multiple inheritance - multiple "parent classes with nonempty slots" cannot be combined. 最大的警告是多重继承-无法合并多个“具有非空插槽的父类”。
To accommodate this restriction, follow best practices: Factor out all but one or all parents' abstraction which their concrete class respectively and your new concrete class collectively will inherit from - giving the abstraction(s) empty slots (just like abstract base classes in the standard library). 为适应此限制,请遵循最佳实践:排除其父母和您的新具体类将分别继承的一个或所有父类的所有抽象-给这些抽象空的位置(就像在父类中的抽象基类一样)标准库)。
See section on multiple inheritance below for an example. 有关示例,请参见下面有关多重继承的部分。
To have attributes named in __slots__
to actually be stored in slots instead of a __dict__
, a class must inherit from object
. 要使在__slots__
命名的属性实际上存储在插槽中而不是在__dict__
存储,类必须从object
继承。
To prevent the creation of a __dict__
, you must inherit from object
and all classes in the inheritance must declare __slots__
and none of them can have a '__dict__'
entry. 为了防止创建__dict__
,您必须从object
继承,并且继承中的所有类都必须声明__slots__
并且它们都不能具有'__dict__'
条目。
There are a lot of details if you wish to keep reading. 如果您想继续阅读,有很多细节。
__slots__
: Faster attribute access. 为什么使用__slots__
:更快的属性访问。 The creator of Python, Guido van Rossum, states that he actually created __slots__
for faster attribute access. Python的创建者Guido van Rossum 说 ,他实际上创建了__slots__
以便更快地访问属性。
It is trivial to demonstrate measurably significant faster access: 证明可观的显着更快访问是微不足道的:
import timeit
class Foo(object): __slots__ = 'foo',
class Bar(object): pass
slotted = Foo()
not_slotted = Bar()
def get_set_delete_fn(obj):
def get_set_delete():
obj.foo = 'foo'
obj.foo
del obj.foo
return get_set_delete
and 和
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
The slotted access is almost 30% faster in Python 3.5 on Ubuntu. 在Ubuntu 3.5上的Python 3.5中,插槽式访问的速度几乎快了30%。
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
In Python 2 on Windows I have measured it about 15% faster. 在Windows上的Python 2中,我测得的速度要快15%。
__slots__
: Memory Savings 为什么使用__slots__
:节省内存 Another purpose of __slots__
is to reduce the space in memory that each object instance takes up. __slots__
另一个目的是减少每个对象实例占用的内存空间。
My own contribution to the documentation clearly states the reasons behind this : 我自己对文档的贡献清楚地说明了背后的原因 :
The space saved over using
__dict__
can be significant. 使用__dict__
节省的空间可能很大。
SQLAlchemy attributes a lot of memory savings to __slots__
. SQLAlchemy将大量内存节省归因于__slots__
。
To verify this, using the Anaconda distribution of Python 2.7 on Ubuntu Linux, with guppy.hpy
(aka heapy) and sys.getsizeof
, the size of a class instance without __slots__
declared, and nothing else, is 64 bytes. 为了验证这一点,请在Ubuntu Linux上使用Python 2.7的Anaconda发行版,并带有guppy.hpy
(又称堆)和sys.getsizeof
,不声明__slots__
且没有其他声明的类实例的大小为64字节。 That does not include the __dict__
. 这不包括__dict__
。 Thank you Python for lazy evaluation again, the __dict__
is apparently not called into existence until it is referenced, but classes without data are usually useless. 再次感谢Python的惰性求值,显然在引用__dict__
之前,它并不存在,但是没有数据的类通常是无用的。 When called into existence, the __dict__
attribute is a minimum of 280 bytes additionally. 当被调用时, __dict__
属性至少要最少280个字节。
In contrast, a class instance with __slots__
declared to be ()
(no data) is only 16 bytes, and 56 total bytes with one item in slots, 64 with two. 相反,声明为()
(无数据)的__slots__
的类实例只有16个字节,并且插槽中有一项的总字节数为56个,插槽中有两项的64个字节。
For 64 bit Python, I illustrate the memory consumption in bytes in Python 2.7 and 3.6, for __slots__
and __dict__
(no slots defined) for each point where the dict grows in 3.6 (except for 0, 1, and 2 attributes): 对于64位Python,我将说明dict在3.6中增长的每个点的__slots__
和__dict__
(未定义插槽)的内存消耗(以字节为单位)以python 2.7和3.6为单位(0、1和2属性除外):
Python 2.7 Python 3.6
attrs __slots__ __dict__* __slots__ __dict__* | *(no slots defined)
none 16 56 + 272? 16 56 + 112? | ?if __dict__ referenced
one 48 56 + 272 48 56 + 112
two 56 56 + 272 56 56 + 112
six 88 56 + 1040 88 56 + 152
11 128 56 + 1040 128 56 + 240
22 216 56 + 3344 216 56 + 408
43 384 56 + 3344 384 56 + 752
So, in spite of smaller dicts in Python 3, we see how nicely __slots__
scale for instances to save us memory, and that is a major reason you would want to use __slots__
. 因此,尽管Python 3中的指令较小,但我们看到__slots__
可以很好地扩展实例以节省内存,这是您想使用__slots__
主要原因。
Just for completeness of my notes, note that there is a one-time cost per slot in the class's namespace of 64 bytes in Python 2, and 72 bytes in Python 3, because slots use data descriptors like properties, called "members". 仅出于我的注意事项的完整性,请注意,在类的名称空间中,每个插槽的一次性成本为Python 2中64字节,而在Python 3中为72字节,因为插槽使用数据描述符(如属性)称为“成员”。
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
: __slots__
演示: To deny the creation of a __dict__
, you must subclass object
: 要拒绝创建__dict__
,必须子类化object
:
class Base(object):
__slots__ = ()
now: 现在:
>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
File "<pyshell#38>", line 1, in <module>
b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'
Or subclass another class that defines __slots__
或子类化另一个定义__slots__
类
class Child(Base):
__slots__ = ('a',)
and now: 现在:
c = Child()
c.a = 'a'
but: 但:
>>> c.b = 'b'
Traceback (most recent call last):
File "<pyshell#42>", line 1, in <module>
c.b = 'b'
AttributeError: 'Child' object has no attribute 'b'
To allow __dict__
creation while subclassing slotted objects, just add '__dict__'
to the __slots__
(note that slots are ordered, and you shouldn't repeat slots that are already in parent classes): 要在创建插槽对象的子类时创建__dict__
,只需在__slots__
添加'__dict__'
(请注意,插槽是有序的,并且您不应重复父类中已经存在的插槽):
class SlottedWithDict(Child):
__slots__ = ('__dict__', 'b')
swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
and 和
>>> swd.__dict__
{'c': 'c'}
Or you don't even need to declare __slots__
in your subclass, and you will still use slots from the parents, but not restrict the creation of a __dict__
: 或者甚至不需要在子类中声明__s