【问题标题】:Why does __slots__ = ('__dict__',) produce smaller instances?为什么 __slots__ = ('__dict__',) 会产生更小的实例?
【发布时间】:2023-03-26 06:00:01
【问题描述】:
class Spam(object):
    __slots__ = ('__dict__',)

产生比“普通”类更小的实例。这是为什么呢?

来源:David Beazley's recent tweet

【问题讨论】:

  • 显然是因为it's missing __dir__。至于为什么会这样,我不知道。
  • 最好用一个例子来更新你的问题。
  • “为什么”很明显,“如何”是 python 跳过创建每个对象的属性dict。您将属性命名为__dict__ 有点奇怪……如果您在__init__ 中使用self.__dict__ = {},那么您就违背了__slots__ 的目的。
  • 在插槽和非插槽版本上运行 dir - 唯一的区别是非插槽版本具有 __weakref__,而不是 __slots__,但在我的测试中它们的行为完全相同
  • 引用来源是有礼貌的。我已将其编辑到问题中。

标签: python class python-3.x


【解决方案1】:

在我看来,节省的内存似乎来自于实例上缺少 __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 占用的存储空间比其内容所需的存储空间总和还要多)。最重要的是,以这种方式使用插槽也有缺点(例如,没有对实例的弱引用)。

【讨论】:

  • 这是因为缺少但另一方面我们在其他情况下有__slots__,所以差异应该等于__slots____weakref__的大小差异,但事实并非如此!
  • @Kasramvd -- 如果我理解正确,__slots__class 上创建描述符,所以 __slots__ 并没有真正被 实例。该实例仅携带一个内存插槽 (sizeof(pointer)) 使用弱引用,我 猜测 getsizeof 还会为 __weakref__ 列表的大小增加一点开销——虽然它很漂亮很难通过来源追踪 getsizeof 实际上在做什么来告诉你它报告的数字:-)
  • 是的,很明显的区别在于您提到的__weakref____slots__。但要找出差异的确切原因并不容易。特别是在 python 级别。
  • ...而我正在回复的评论在我发布之前就消失了。看来我太慢了。
  • @user2357112 -- 是的,我在脑海中解决了“__weakref__ 插槽是另一个插槽”的一般引用并删除了评论(尽管对我的深层内部工作了解较少很高兴你在这里提供。)
猜你喜欢
  • 1970-01-01
  • 2017-07-14
  • 2012-03-16
  • 2012-05-20
  • 2019-11-16
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多