Python中__slots__
的目的是什么-尤其是关于何时以及何时不使用它的目的?
#1楼
插槽对于库调用非常有用,以消除进行函数调用时的“命名方法分派”。 SWIG 文档中对此进行了提及。 对于想要减少使用插槽的通常称为函数的函数开销的高性能库,速度要快得多。
现在,这可能与OP问题没有直接关系。 它与构建扩展有关,而不是与在对象上使用slot语法有关。 但这确实有助于完整了解插槽的使用情况以及它们背后的一些原因。
#2楼
类实例的属性具有3个属性:实例,属性名称和属性值。
在常规属性访问中 ,实例充当字典,而属性名称充当该字典中查找值的键。
instance(attribute)->值
在__slots__ access中 ,属性名称充当字典,实例充当字典中查找值的键。
属性(实例)->值
在flyweight模式中 ,属性的名称充当字典,而值充当该字典中查找实例的键。
attribute(value)->实例
#3楼
在Python中,
__slots__
的目的是什么?在什么情况下应避免这种情况?
TLDR:
特殊属性__slots__
允许您显式说明您希望对象实例具有哪些实例属性,并具有预期的结果:
- 更快的属性访问。
- 节省内存空间 。
节省的空间来自
- 将值引用存储在插槽中而不是
__dict__
。 - 如果父类拒绝
__dict__
和__weakref__
而您声明__slots__
拒绝创建。
快速警告
请注意,您只应在继承树中一次声明一个特定的插槽。 例如:
class Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
遇到错误时,Python不会反对(它应该会),否则问题可能不会显现出来,但是您的对象将比原先占用更多的空间。
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(64, 80)
最大的警告是多重继承-无法合并多个“具有非空插槽的父类”。
为适应此限制,请遵循最佳实践:排除其父母和您的新具体类将分别继承的一个或所有父类的所有抽象-给这些抽象空的位置(就像在父类中的抽象基类一样)标准库)。
有关示例,请参见下面有关多重继承的部分。
要求:
要使在
__slots__
命名的属性实际上存储在插槽中而不是在__dict__
存储,类必须从object
继承。为了防止创建
__dict__
,您必须从object
继承,并且继承中的所有类都必须声明__slots__
并且它们都不能具有'__dict__'
条目。
如果您想继续阅读,有很多细节。
为什么使用__slots__
:更快的属性访问。
Python的创建者Guido van Rossum 说 ,他实际上创建了__slots__
以便更快地访问属性。
证明可观的显着更快访问是微不足道的:
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
和
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
在Ubuntu 3.5上的Python 3.5中,插槽式访问的速度几乎快了30%。
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
在Windows上的Python 2中,我测得的速度要快15%。
为什么使用__slots__
:节省内存
__slots__
另一个目的是减少每个对象实例占用的内存空间。
使用
__dict__
节省的空间可能很大。
SQLAlchemy将大量内存节省归因于__slots__
。
为了验证这一点,请在Ubuntu Linux上使用Python 2.7的Anaconda发行版,并带有guppy.hpy
(又称堆)和sys.getsizeof
,不声明__slots__
且没有其他声明的类实例的大小为64字节。 这不包括__dict__
。 再次感谢Python的惰性求值,显然在引用__dict__
之前,它并不存在,但是没有数据的类通常是无用的。 当被调用时, __dict__
属性至少要最少280个字节。
相反,声明为()
(无数据)的__slots__
的类实例只有16个字节,并且插槽中有一项的总字节数为56个,插槽中有两项的64个字节。
对于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
因此,尽管Python 3中的指令较小,但我们看到__slots__
可以很好地扩展实例以节省内存,这是您想使用__slots__
主要原因。
仅出于我的注意事项的完整性,请注意,在类的名称空间中,每个插槽的一次性成本为Python 2中