__slots__ =('__ dict__')为什么产生较小的实例?

Mar*_*nen 4 python class python-3.x

class Spam(object):
    __slots__ = ('__dict__',)
Run Code Online (Sandbox Code Playgroud)

生成小于"普通"类的实例.为什么是这样?

资料来源:David Beazley最近发布的推文.

mgi*_*son 9

对我来说,看起来节省的内存来自__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))
Run Code Online (Sandbox Code Playgroud)

结果(在python 3.5.2上)是:

Spam1 48
Spam2 56
Spam3 48
Eggs 56
Run Code Online (Sandbox Code Playgroud)

我们看到Spam2(有一个__weakref__)与Eggs(传统类)的大小相同.

请注意,通常情况下,这种节省将完全无关紧要(并且可以防止您在启用插槽的类中使用弱引用).一般来说,节约来自__slots__于他们不__dict__首先创造一个事实.由于__dict__使用稍微稀疏的表来实现(为了帮助避免散列冲突并维护O(1)查找/插入/删除),因此程序创建的每个字典都没有使用相当大的空间.如果你添加'__dict__'到你的__slots__,你会错过这个优化(仍然创建一个dict).

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

class Spam3(object):
    __slots__ = ('foo', 'bar')
Run Code Online (Sandbox Code Playgroud)

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

Spam1 48
Spam2 56
Spam3 56
Eggs 56
Run Code Online (Sandbox Code Playgroud)

因此每个插槽在实例上占用8个字节(对我来说 - 可能因为sizeof(pointer)我的系统上有8个字节).还要注意,这__slots__是通过制作描述符(它们存在于而不是实例中)来实现的.因此,实例(即使你可能找到__slots__列出的via dir(instance))实际上并没有携带一个__slots__值) - 这是由班级带来的.

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

class Foo(object):
    __slots__ = ('foo',)
    foo = 'bar'
Run Code Online (Sandbox Code Playgroud)

所以把它煮沸:

  • 实例上的每个"槽"占用系统上指针的大小.
  • 没有__slots__ = ('__dict__',)一个__dict__槽和__weakref__槽上的实例创建
  • with __slots__ = ('__dict__',),__dict__创建一个__weakref__插槽但在实例上不创建插槽.
  • 在任何情况下都不会__slots__实际放在实例上.它生活在课堂上(即使你可能会看到它dir(instance)).
  • 通过__slots__这种方式获得的节省可能是微不足道的.__slots__当你没有dict为实例创建一个实际节省时(由于dict数据结构中的包装数据有些稀疏,因此占用的存储量超过了其内容所需存储量的总和).最重要的是,以这种方式使用插槽存在缺点(例如,没有对您的实例的弱引用).