子类中__slots__的继承实际上如何工作?


68

插槽上Python数据模型参考部分中,有关于使用的注释列表__slots__。我对第一项和第六项感到完全困惑,因为它们似乎相互矛盾。

第一项:

  • 从没有的类继承时,该类 __slots____dict__属性将始终可访问,因此__slots__ 子类中的定义是没有意义的。

第六项:

  • __slots__ 声明的动作仅限于定义它的类。结果,子类将具有一个,__dict__ 除非它们也定义__slots__ (该子类只能包含任何其他插槽的名称)。

在我看来,这些项目的措词可能更好,也可以通过代码显示,但我一直在努力解决这个问题,但仍然感到困惑。我不明白怎么__slots__应该被使用,而我试图让他们的工作更好地把握。

问题:

有人可以用通俗易懂的语言向我解释子类继承时继承插槽的条件是什么?

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

Answers:


114

就像其他人提到的那样,定义的唯一原因__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

由于字典本身仅占用140个字节,因此显然n声称“ 32字节”对象并未考虑每个实例涉及的所有内容。您可以使用pympler等第三方扩展来做得更好

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

这可以更清楚地显示出节省的内存空间__slots__:对于这样的简单对象,它不到200个字节,几乎是对象总空间的2/3。现在,由于这些天或多或少的兆字节对大多数应用程序来说并没有多大关系,所以这也告诉您,__slots__如果一次仅要拥有数千个实例,那是不值得的。但是,对于数百万个实例,它确实的确具有非常重要的意义。您还可以实现微观上的加速(部分原因是通过使用可以更好地对小型对象使用缓存__slots__):

$ 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,我看到一个较大的相对优势,__slots__设置的属性,但根本没有,确实是一个微小的DIS优势,为得到它)。

现在,关于继承:为了使实例无字典,其继承链中的所有类也必须具有无字典的实例。具有无dict实例的类是那些定义__slots__,加上大多数内置类型的类(其实例具有dict的内置类型是可以在其实例上设置任意属性(例如函数)的那些类型)。插槽名称中的重叠不被禁止,但是它们是无用的,并且浪费了一些内存,因为插槽是继承的:

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

如您所见,您可以aAB实例上设置属性-AB本身仅定义slot b,但它继承aA。禁止重复继承的插槽:

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

但确实浪费了一点内存:

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

因此,实际上没有理由这样做。


2
根据docs docs.python.org/2/reference/datamodel.html#slots的说明: “如果类定义了也在基类中定义的插槽,则无法访问基类插槽定义的实例变量(除非检索其描述符)直接来自基类)。这使程序的含义不确定。将来,可能会添加检查以防止这种情况。” -这种不确定的行为让我有些担心-我已经询问了该问题(stackoverflow.com/q/41159714/281545),但没有得到答案
Mr_and_Mrs_D

创造无字典
绝佳

1
插槽还可以确保类的对象不会获得不需要的属性。
sshilovsky

我不会说唯一的原因-像PyCharm或linter这样的IDE可以在执行代码之前使用插槽来查找代码中的错误。
cz

17
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__在不久的将来不需要使用。它只是为了节省内存而以某种灵活性为代价。除非您有成千上万的对象,否则没有关系。


7

Python:__slots__子类中的继承实际上如何工作?

我对第一项和第六项感到完全困惑,因为它们似乎相互矛盾。

这些项目实际上并不相互矛盾。第一个涉及未实现__slots__的类的子类,第二个涉及确实实现的类的子类__slots__

未实现的类的子类 __slots__

我越来越意识到,(正确地)像Python文档一样好,它们也不是完美的,尤其是在语言使用较少的功能方面。我将更改文档如下:

当从没有继承的类继承时,该类__slots____dict__属性将始终可访问,因此__slots__子类中的定义是没有意义的

__slots__对于这样的课程仍然有意义。它记录了类的属性的预期名称。它还为这些属性创建了插槽-它们将获得更快的查找并使用更少的空间。它仅允许其他属性,这些属性将分配给__dict__

更改已被接受,现在已在最新文档中

这是一个例子:

class Foo: 
    """instances have __dict__"""

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

Bar它不仅具有声明的插槽,还具有Foo的插槽-包括__dict__

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

确实实现的类的子类__slots__

__slots__声明的动作仅限于定义它的类。结果,子类将具有一个,__dict__除非它们也定义__slots__(该子类只能包含任何其他插槽的名称)。

嗯,那也不完全正确。__slots__声明的动作并不完全限于定义它的类。例如,它们可能对多重继承有影响。

我将其更改为:

对于定义的继承树中的__slots__子类将具有,__dict__除非它们也进行了定义__slots__(该子类只能包含任何其他插槽的名称)。

我实际上已将其更新为:

__slots__声明的动作不限于定义它的类。__slots__父母声明的孩子类别中可以使用。但是,子子类将获得一个__dict__和, __weakref__除非它们也进行了定义__slots__(该子类仅应包含任何其他插槽的名称)。

这是一个例子:

class Foo:
    __slots__ = 'foo'

class Bar(Foo):
    """instances get __dict__ and __weakref__"""

而且我们看到了,插槽化类的子类可以使用插槽:

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

(有关更多信息__slots__请参见此处。)


2

从您链接的答案中:

正确的用法__slots__是节省对象空间。而不是动态的命令...

“从不具有该类的类继承时,将始终可以访问该类__slots____dict__属性”,因此添加您自己的类__slots__不能防止对象具有__dict__,也不能节省空间。

关于__slots__不被继承的观点有点钝。请记住,这是一个魔术属性,其行为不像其他属性,然后重新读一遍,就像说该魔术插槽行为不是继承的一样。(实际上就是全部。)


同意这有点笨拙,并且肯定可以从您的回答简洁中受益。谢谢。
贾森主义

1

我的理解如下:

  • classX没有__dict__ <------->类,X并且其超类都已__slots__指定

  • 在这种情况下,该类的实际位置由的__slots__声明的联合X及其超类组成;如果此联合不脱节,则行为是不确定的(并且将成为错误)


2
union is not disjoint不是普通英语的短语。:)正如@Alex Martelli所示,如果插槽集不脱节,这不是错误。否则-不错的总结。
jfs

@JFSebastian如果它们不相交,则“程序的含义是不确定的”。文档明确指出了这一点。
威林
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.