简体   繁体   English

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

[英]How does inheritance of __slots__ in subclasses actually work?

In the Python data model reference section on slots there is a list of notes on using __slots__ .关于插槽Python 数据模型参考部分中,有一个关于使用__slots__的注意事项列表。 I am thoroughly confused by the 1st and 6th items, because they seem to be contradicting each other.我对第 1 项和第 6 项感到非常困惑,因为它们似乎相互矛盾。

First item:第一项:

  • When inheriting from a class without __slots__ , the __dict__ attribute of that class will always be accessible, so a __slots__ definition in the subclass is meaningless.从没有__slots__的类继承时,该类的__dict__属性将始终可访问,因此子类中的__slots__定义是没有意义的。

Sixth item:第六项:

  • The action of a __slots__ declaration is limited to the class where it is defined. __slots__声明的作用仅限于定义它的类。 As a result, subclasses will have a __dict__ unless they also define __slots__ (which must only contain names of any additional slots).因此,子类将有一个__dict__除非它们还定义了__slots__ (它必须只包含任何其他插槽的名称)。

It seems to me these items could be better worded or shown through code, but I have been trying to wrap my head around this and am still coming up confused.在我看来,这些项目可以用更好的措辞或通过代码显示,但我一直在努力解决这个问题,但仍然感到困惑。 I do understand how __slots__ are supposed to be used , and I am trying to get a better grasp on how they work.我确实了解__slots__ 应该如何使用,并且我正在努力更好地了解它们的工作方式。

The Question:问题:

Can someone please explain to me in plain language what the conditions are for inheritance of slots when subclassing?有人可以用简单的语言向我解释在子类化时继承插槽的条件是什么吗?

(Simple code examples would be helpful but not necessary.) (简单的代码示例会有所帮助,但不是必需的。)

As others have mentioned, the sole reason for defining __slots__ is to save some memory, when you have simple objects with a predefined set of attributes and don't want each to carry around a dictionary.正如其他人所提到的,定义__slots__的唯一原因是节省一些内存,当您拥有具有预定义属性集的简单对象并且不希望每个对象都带有字典时。 This is meaningful only for classes of which you plan to have many instances, of course.当然,这仅对您计划拥有多个实例的类有意义。

The savings may not be immediately obvious -- consider...:节省的成本可能不会立即显现——考虑......:

>>> 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

From this, it would seem the with-slots size is larger than the no-slots size!由此看来,这似乎与该型槽尺寸比无槽尺寸更大 But that's a mistake, because sys.getsizeof doesn't consider "object contents" such as the dictionary:但这是一个错误,因为sys.getsizeof不考虑“对象内容”,例如字典:

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

Since the dict alone takes 140 bytes, clearly the "32 bytes" object n is alleged to take are not considering all that's involved in each instance.由于单独的 dict 需要 140 个字节,因此显然“32 个字节”对象n没有考虑每个实例中涉及的所有内容。 You can do a better job with third-party extensions such as pympler :您可以使用第三方扩展(例如pympler )做得更好

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

This shows much more clearly the memory footprint that's saved by __slots__ : for a simple object such as this case, it's a bit less than 200 bytes, almost 2/3 of the object's overall footprint.这更清楚地显示了__slots__节省的内存占用:对于像这种情况这样的简单对象,它__slots__小于 200 字节,几乎是对象总占用空间的 2/3。 Now, since these days a megabyte more or less doesn't really matter all that much to most applications, this also tells you that __slots__ is not worth the bother if you're going to have just a few thousand instances around at a time -- however, for millions of instances, it sure does make a very important difference.现在,由于如今对于大多数应用程序而言,或多或少的兆字节并不那么重要,这也告诉您__slots__如果您一次只有几千个实例,则不值得打扰 - - 然而,对于数百万个实例,它确实产生了非常重要的差异。 You can also get a microscopic speedup (partly due to better cache use for small objects with __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

but this is somewhat dependent on Python version (these are the numbers I measure repeatably with 2.5; with 2.6, I see a larger relative advantage to __slots__ for setting an attribute, but none at all, indeed a tiny dis advantage, for getting it).但是这一定程度上取决于Python版本(这些都是我用2.5重复测量号;用2.6,我看到一个较大的相对优势__slots__设置的属性,但根本没有,确实是一个微小的DIS优势,得到它) .

Now, regarding inheritance: for an instance to be dict-less, all classes up its inheritance chain must also have dict-less instances.现在,关于继承:对于无字典的实例,其继承链上的所有类也必须具有无字典的实例。 Classes with dict-less instances are those which define __slots__ , plus most built-in types (built-in types whose instances have dicts are those on whose instances you can set arbitrary attributes, such as functions).具有 dict-less 实例的类是那些定义__slots__ ,以及大多数内置类型(实例具有 dict 的内置类型是那些可以在其实例上设置任意属性的类,例如函数)。 Overlaps in slot names are not forbidden, but they're useless and waste some memory, since slots are inherited:插槽名称中的重叠是不被禁止的,但它们是无用的并且会浪费一些内存,因为插槽是继承的:

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

as you see, you can set attribute a on an AB instance -- AB itself only defines slot b , but it inherits slot a from A .如您所见,您可以在AB实例上设置属性a - AB本身仅定义槽b ,但它从A继承槽a Repeating the inherited slot isn't forbidden:不禁止重复继承的插槽:

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

but does waste a little memory:但确实浪费了一点内存:

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

so there's really no reason to do it.所以真的没有理由这样做。

class WithSlots(object):
    __slots__ = "a_slot"

class NoSlots(object):       # This class has __dict__
    pass

First Item第一项

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

Sixth Item第六项

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.

You probably won't need to use __slots__ in the near future.在不久的将来,您可能不需要使用__slots__ It's only intended to save memory at the cost of some flexibility.它只是为了以牺牲一些灵活性为代价来节省内存。 Unless you have tens of thousands of objects it won't matter.除非您有数以万计的对象,否则这无关紧要。

Python: How does inheritance of __slots__ in subclasses actually work? Python:子类中__slots__继承实际上是如何工作的?

I am thoroughly confused by the 1st and 6th items, because they seem to be contradicting each other.我对第 1 项和第 6 项感到非常困惑,因为它们似乎相互矛盾。

Those items don't actually contradict each other.这些项目实际上并不相互矛盾。 The first regards subclasses of classes that don't implement __slots__ , the second regards subclasses of classes that do implement __slots__ .的不实现类的第一个方面的子类__slots__ ,该的类第二问候子类实现__slots__

Subclasses of classes that don't implement __slots__未实现__slots__的类的子类

I am increasingly aware that as great as the Python docs are (rightly) reputed to be, they are not perfect, especially regarding the less used features of the language.我越来越意识到,尽管 Python 文档(正确地)被认为是伟大的,但它们并不完美,尤其是关于该语言较少使用的功能。 I would alter the docs as follows:我会改变文档如下:

When inheriting from a class without __slots__ , the __dict__ attribute of that class will always be accessible , so a __slots__ definition in the subclass is meaningless .从没有__slots__的类继承时,该类的__dict__属性将始终可访问 ,因此子类中的 __slots__定义是没有意义的

__slots__ is still meaningful for such a class. __slots__对于这样的类仍然有意义。 It documents the expected names of attributes of the class.它记录了类属性的预期名称。 It also creates slots for those attributes - they will get the faster lookups and use less space.它还为这些属性创建插槽 - 它们将获得更快的查找并使用更少的空间。 It just allows for other attributes, which will be assigned to the __dict__ .它只允许其他属性,这些属性将分配给__dict__

This change has been accepted and is now in the latest documentation .更改已被接受,现在在最新的文档中

Here's an example:下面是一个例子:

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

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

Bar not only has the slots it declares, it also has Foo's slots - which include __dict__ : Bar不仅有它声明的槽,它还有 Foo 的槽——包括__dict__

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

Subclasses of classes that do implement __slots__类子类实现__slots__

The action of a __slots__ declaration is limited to the class where it is defined. __slots__声明的作用仅限于定义它的类。 As a result, subclasses will have a __dict__ unless they also define __slots__ (which must only contain names of any additional slots).因此,子类将有一个__dict__除非它们还定义了__slots__ (它必须只包含任何其他插槽的名称)。

Well that's not quite right either.嗯,这也不完全正确。 The action of a __slots__ declaration is not entirely limited to the class where it is defined. __slots__声明的作用完全限于定义它的类。 They can have implications for multiple inheritance, for example.例如,它们可能对多重继承产生影响。

I would change that to:我会将其更改为:

For classes in an inheritance tree that defines __slots__ , subclasses will have a __dict__ unless they also define __slots__ (which must only contain names of any additional slots).对于定义__slots__的继承树中的子类将具有__dict__除非它们还定义了__slots__ (它必须只包含任何其他插槽的名称)。

I have actually updated it to read:我实际上已将其更新为:

The action of a __slots__ declaration is not limited to the class where it is defined. __slots__声明的作用不限于定义它的类。 __slots__ declared in parents are available in child classes.在父类中声明的__slots__在子类中可用。 However, child subclasses will get a __dict__ and __weakref__ unless they also define __slots__ (which should only contain names of any additional slots).然而,孩子的子类将得到一个__dict____weakref__ ,除非他们还定义__slots__ (它应该只包含任何额外的插槽的名称)。

Here's an example:下面是一个例子:

class Foo:
    __slots__ = 'foo'

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

And we see that a subclass of a slotted class gets to use the slots:我们看到槽类的子类可以使用槽:

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

(For more on __slots__ , see my answer here .) (有关__slots__更多信息, 请在此处查看我的回答。)

From the answer you linked:从您链接的答案中:

The proper use of __slots__ is to save space in objects. __slots__的正确使用是为了节省对象的空间。 Instead of having a dynamic dict...而不是有一个动态的字典......

"When inheriting from a class without __slots__ , the __dict__ attribute of that class will always be accessible", so adding your own __slots__ cannot prevent objects from having a __dict__ , and cannot save space. “从没有__slots__的类继承时,该类的__dict__属性将始终可访问”,因此添加您自己的__slots__不能阻止对象具有__dict__ ,也不能节省空间。

The bit about __slots__ not being inherited is a little obtuse.关于__slots__不被继承的一点有点迟钝。 Remember that it's a magic attribute and doesn't behave like other attributes, then re-read that as saying this magic slots behavior isn't inherited.请记住,它是一个魔法属性,它的行为不像其他属性,然后重新阅读它,说这个魔法插槽行为不是继承的。 (That's really all there is to it.) (这就是它的全部内容。)

My understanding is as follows:我的理解如下:

  • class X has no __dict__ <-------> class X and its superclasses all have __slots__ specifiedX没有__dict__ <------->X及其超类都指定了__slots__

  • in this case, the actual slots of the class are comprised from the union of __slots__ declarations for X and its superclasses;在这种情况下,类的实际插槽由X及其超类的__slots__声明的并集组成; the behavior is undefined (and will become an error) if this union is not disjoint如果这个联合不是不相交的,则行为是未定义的(并且将成为错误)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM