python - 为什么 __slots__ = ('__dict__' ,) 会产生更小的实例?

标签 python class python-3.x

class Spam(object):
    __slots__ = ('__dict__',)

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

来源:David Beazley's recent tweet .

最佳答案

对我来说,内存节省似乎是因为实例上缺少 __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__ 中,您将错过此优化(仍然会创建一个字典)。

为了进一步探索这一点,我们可以添加更多插槽:

class Spam3(object):
    __slots__ = ('foo', 'bar')

现在如果我们重新运行,我们看到它需要:

Spam1 48
Spam2 56
Spam3 56
Eggs 56

因此每个插槽在实例 上占用 8 个字节(对我来说——可能是因为在我的系统上 8 个字节是 sizeof(pointer))。另请注意,__slots__ 是通过创建描述符(存在于,而不是实例)来实现的。因此,该实例(即使您可能会发现 __slots__ 通过 dir(instance) 列出)实际上并没有携带 __slots__ 值)- - 这是由携带的。

这也会导致您启用插槽的类无法设置“默认”值...例如以下代码不起作用:

class Foo(object):
    __slots__ = ('foo',)
    foo = 'bar'

总结一下:

  • 实例上的每个“插槽”占用系统上指针的大小。
  • 没有 __slots__ = ('__dict__',) 在实例上创建一个 __dict__ 槽和一个 __weakref__
  • 使用 __slots__ = ('__dict__',),创建了一个 __dict__ 槽,但没有在实例上创建一个 __weakref__ 槽。
  • 在这两种情况下,__slots__ 实际上都没有放在实例 上。它存在于 中(即使您可能从 dir(instance) 看到它)。
  • 以这种方式使用 __slots__ 所节省的费用可能微不足道。 __slots__ 的真正节省发生在您不为实例创建 dict 时(因为 dict 占用的存储空间大于存储空间的总和由于数据结构中的打包数据有些稀疏,因此它们的内容是必需的)。最重要的是,以这种方式使用槽也有缺点(例如,没有对您的实例的弱引用)。

关于python - 为什么 __slots__ = ('__dict__' ,) 会产生更小的实例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40003067/

相关文章:

python - 在 Jupyter Notebook 中以正确的顺序获取日志输出和标准输出

PHP 扩展 DOMDocument 不接受 $config 数组

c# - c# - 如何让一个类对象存储另一个类对象?

python - 使用 python pandas 读取 .reg 文件

python - 如何更改 NDB 记录的祖先?

python - 将一列 int64 (YYYYMMDDHHMMSS) 转换为不带分隔符的 datetime64

php - 从另一个 PHP 类设置 protected 变量的正确方法

python - 使用 numpy 根据数组的条件索引创建矩阵

python-3.x - 单击 odoo 12 中的按钮后关闭向导

python - 使用 lxml `.xpath()` 和 `for` 的意外输出