在我看来,节省的内存似乎来自于实例上缺少 __weakref__。
如果我们有:
class Spam1(object):
__slots__ = ('__dict__',)
class Spam2(object):
__slots__ = ('__dict__', '__weakref__')
class Spam3(object):
__slots__ = ('foo',)
class Eggs(object):
pass
objs = Spam1(), Spam2(), Spam3(), Eggs()
for obj in objs:
obj.foo = 'bar'
import sys
for obj in objs:
print(type(obj).__name__, sys.getsizeof(obj))
结果(在 python 3.5.2 上)是:
Spam1 48
Spam2 56
Spam3 48
Eggs 56
我们看到Spam2(具有__weakref__)与Eggs(传统类)大小相同。
请注意,通常情况下,这种节省完全是微不足道的(并且会阻止您在启用插槽的类中使用弱引用)。一般来说,__slots__ 的节省是因为他们一开始就没有创建__dict__。由于__dict__ 是使用稍微稀疏的表实现的(为了帮助避免散列冲突并维护 O(1) 查找/插入/删除),因此您的程序创建的每个字典都没有使用相当多的空间.但是,如果您将 '__dict__' 添加到您的 __slots__ 中,您将错过此优化(仍会创建一个 dict)。
为了进一步探索这一点,我们可以添加更多插槽:
class Spam3(object):
__slots__ = ('foo', 'bar')
现在如果我们重新运行,我们看到它需要:
Spam1 48
Spam2 56
Spam3 56
Eggs 56
所以 instance 上的每个插槽占用 8 个字节(对我来说——可能是因为 8 个字节在我的系统上是 sizeof(pointer))。另请注意,__slots__ 是通过制作描述符(位于 类 上,而不是实例上)来实现的。因此,实例(即使您可能会发现通过 dir(instance) 列出的 __slots__)实际上并没有携带 __slots__ 值)——这是由 类携带的。 p>
这也导致您的插槽启用类无法设置“默认”值......例如以下代码不起作用:
class Foo(object):
__slots__ = ('foo',)
foo = 'bar'
所以归结为:
- 实例上的每个“槽”占用系统上指针的大小。
- 在实例上创建没有
__slots__ = ('__dict__',) 和__dict__ 插槽和__weakref__ 插槽
- 使用
__slots__ = ('__dict__',),会创建__dict__ 插槽,但不会在实例上创建__weakref__ 插槽。
- 在这两种情况下,
__slots__ 都没有实际放在 instance 上。它存在于 class 中(尽管您可能会从 dir(instance) 看到它)。
- 以这种方式使用
__slots__ 所节省的成本可能微不足道。当您不为实例创建dict 时,__slots__ 的真正节省会发生(因为由于数据结构中的打包数据有些稀疏,dict 占用的存储空间比其内容所需的存储空间总和还要多)。最重要的是,以这种方式使用插槽也有缺点(例如,没有对实例的弱引用)。