代码之家  ›  专栏  ›  技术社区  ›  jathanism

python:子类中的槽继承是如何工作的?

  •  57
  • jathanism  · 技术社区  · 15 年前

    Python data model reference section on slots 有一个关于使用的注释列表 __slots__ .我对第1和第6项完全困惑,因为它们似乎相互矛盾。

    第一项:

    • 从类继承时 斯洛茨基 , the __dict__ 属性 那类人永远都是 无障碍,所以 斯洛茨基 子类中的定义是 无意义的。

    第六项:

    • A的作用 斯洛茨基 声明仅限于班级 它的定义。因此, 子类将具有 第二节 除非他们也定义 斯洛茨基 (只能包含 附加插槽)。

    在我看来,这些项目可以更好地用词或通过代码显示,但我一直在努力把我的头围绕在这一点上,并且仍然困惑。我真的明白 斯洛茨基 supposed to be used 我正在努力更好地掌握它们的工作原理。

    问题:

    有人能用通俗易懂的语言向我解释子类化时插槽继承的条件吗?

    (简单的代码示例将有帮助,但不是必需的。)

    5 回复  |  直到 6 年前
        1
  •  99
  •   Alex Martelli    15 年前

    正如其他人提到的,定义 __slots__ 是为了节省一些内存,当您有一组预定义属性的简单对象,并且不希望每个对象都携带字典时。当然,这只对您计划拥有许多实例的类有意义。

    节省的成本可能不会立即显而易见——考虑一下……

    >>> class NoSlots(object): pass
    ... 
    >>> n = NoSlots()
    >>> class WithSlots(object): __slots__ = 'a', 'b', 'c'
    ... 
    >>> w = WithSlots()
    >>> n.a = n.b = n.c = 23
    >>> w.a = w.b = w.c = 23
    >>> sys.getsizeof(n)
    32
    >>> sys.getsizeof(w)
    36
    

    从这个角度看,带槽的尺寸是 更大的 比没有插槽的尺寸大!但那是个错误,因为 sys.getsizeof 不考虑“对象内容”,如字典:

    >>> sys.getsizeof(n.__dict__)
    140
    

    由于dict单独占用140个字节,显然是“32个字节”对象 n 被指控采取不考虑所有涉及到每一个案件。你可以通过第三方扩展来做得更好,比如 pympler :

    >>> import pympler.asizeof
    >>> pympler.asizeof.asizeof(w)
    96
    >>> pympler.asizeof.asizeof(n)
    288
    

    这更清楚地显示了 斯洛茨基 :对于像本例这样的简单对象,它的大小略小于200字节,几乎是该对象总占用空间的2/3。现在,由于现在对大多数应用程序来说,兆字节或多或少并不重要,这也告诉您 斯洛茨基 如果一次只有几千个实例的话,就不值得麻烦了——然而,对于数百万个实例来说,它确实会产生非常重要的影响。您还可以得到一个微观的加速(部分原因是更好地使用缓存来处理具有 斯洛茨基 ):

    $ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x'
    10000000 loops, best of 3: 0.37 usec per loop
    $ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x'
    1000000 loops, best of 3: 0.604 usec per loop
    $ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x=45'
    1000000 loops, best of 3: 0.28 usec per loop
    $ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x=45'
    1000000 loops, best of 3: 0.332 usec per loop
    

    但这在某种程度上取决于Python版本(这些是我用2.5重复测量的数字;而用2.6,我看到了一个更大的相对优势 斯洛茨基 对于 设置 一种属性,但根本没有,实际上很小 数字化信息系统 优势,为 得到 它)。

    现在,关于继承:例如,不听写, 全部的 继承链上的类也必须有无dict的实例。具有无dict实例的类是定义 斯洛茨基 ,再加上大多数内置类型(其实例具有dict的内置类型是可以在其实例上设置任意属性的类型,如函数)。插槽名称中的重叠不是禁止的,但它们是无用的,浪费了一些内存,因为插槽是继承的:

    >>> class A(object): __slots__='a'
    ... 
    >>> class AB(A): __slots__='b'
    ... 
    >>> ab=AB()
    >>> ab.a = ab.b = 23
    >>> 
    

    如您所见,您可以设置属性 a 关于一个 AB 实例—— 抗体 本身只定义槽 b 但它继承了时隙 A . 不禁止重复继承的插槽:

    >>> class ABRed(A): __slots__='a','b'
    ... 
    >>> abr=ABRed()
    >>> abr.a = abr.b = 23
    

    但是会浪费一点记忆:

    >>> pympler.asizeof.asizeof(ab)
    88
    >>> pympler.asizeof.asizeof(abr)
    96
    

    所以真的没有理由这么做。

        2
  •  13
  •   Jonas Kölker    11 年前
    class WithSlots(object):
        __slots__ = "a_slot"
    
    class NoSlots(object):       # This class has __dict__
        pass
    

    第一项

    class A(NoSlots):            # even though A has __slots__, it inherits __dict__
        __slots__ = "a_slot"     # from NoSlots, therefore __slots__ has no effect
    

    第六项

    class B(WithSlots):          # This class has no __dict__
        __slots__ = "some_slot"
    
    class C(WithSlots):          # This class has __dict__, because it doesn't
        pass                     # specify __slots__ even though the superclass does.
    

    你可能不需要使用 __slots__ 在不久的将来。它只是为了节省内存而牺牲了一些灵活性。除非你有数万件物品,否则没关系。

        3
  •  4
  •   Aaron Hall    6 年前

    巨蟒:如何继承 __slots__ 在子类中实际工作?

    我对第1和第6项完全困惑,因为它们似乎相互矛盾。

    这些项目实际上并不矛盾。第一个问题是不实现的类的子类 斯洛茨基 第二个是关于类的子类 实施 斯洛茨基 .

    不实现的类的子类 斯洛茨基

    我越来越意识到,尽管Python文档(正确地)被认为是伟大的,但它们并不完美,特别是在语言使用较少的特性方面。我会改变 docs 如下:

    从类继承时 斯洛茨基 , the __dict__ 属性 那类的总是可以访问的 如此 斯洛茨基 定义在 子类没有意义 .

    斯洛茨基 对这样一个班来说仍然有意义。它记录类属性的预期名称。它也 创造 这些属性的插槽-它们将获得更快的查找速度并使用更少的空间。它只允许其他属性,这些属性将分配给 第二节 .

    这个 change has been accepted 现在在 latest documentation .

    下面是一个例子:

    class Foo: 
        """instances have __dict__"""
    
    class Bar(Foo):
        __slots__ = 'foo', 'bar'
    

    Bar 不仅有它声明的槽,它还有foo的槽,其中包括 第二节 :

    >>> b = Bar()
    >>> b.foo = 'foo'
    >>> b.quux = 'quux'
    >>> vars(b)
    {'quux': 'quux'}
    >>> b.foo
    'foo'
    

    类的子类 实施 斯洛茨基

    A的作用 斯洛茨基 声明仅限于声明所在的类 定义。因此,子类将具有 第二节 除非他们 还定义 斯洛茨基 (必须只包含任何附加名称 插槽)。

    嗯,那也不太对。A的作用 斯洛茨基 声明是 完全局限于定义它的类。例如,它们可以对多重继承产生影响。

    我会把它改为:

    对于继承树中定义 斯洛茨基 , 子类将具有 第二节 除非他们 还定义 斯洛茨基 (必须只包含任何附加名称 插槽)。

    我已经将其更新为:

    A的作用 斯洛茨基 声明不限于类 它的定义。 斯洛茨基 在家长中声明可在 子类。但是,子类将得到 第二节 __weakref__ 除非他们也定义 斯洛茨基 (只应包含任何附加插槽的名称)。

    下面是一个例子:

    class Foo:
        __slots__ = 'foo'
    
    class Bar(Foo):
        """instances get __dict__ and __weakref__"""
    

    我们看到slottle类的一个子类开始使用slots:

    >>> b = Bar()
    >>> b.foo = 'foo'
    >>> b.bar = 'bar'
    >>> vars(b)
    {'bar': 'bar'}
    >>> b.foo
    'foo'
    

    (更多关于 斯洛茨基 , see my answer here )

        4
  •  2
  •   Roger Pate    15 年前

    从你链接的答案中:

    正确使用 __slots__ 是为了节省对象的空间。而不是动态听写…

    “从类继承时 斯洛茨基 , the __dict__ 该类的属性将始终是可访问的”,因此添加您自己的 斯洛茨基 无法阻止对象具有 第二节 ,无法节省空间。

    关于某事 斯洛茨基 不被继承有点迟钝。记住这是一个魔法属性,它的行为与其他属性不同,然后重新解读为说这个魔法插槽行为不是继承的。(这就是它的全部内容。)

        5
  •  1
  •   ilya n.    15 年前

    我的理解如下:

    • X 没有 __dict__ <-------> X 它的超类都有 __slots__ 明确规定

    • 在这种情况下,类的实际槽由 斯洛茨基 声明 X 以及它的超类;如果这个联合不分离,则行为是未定义的(并且将成为一个错误)。