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

发布于 2024-08-13 01:58:57 字数 810 浏览 7 评论 0原文

关于槽的 Python 数据模型参考部分 中,有一个关于使用的注释列表__slots__。我对第 1 条和第 6 条感到非常困惑,因为它们似乎相互矛盾。

第一项:

  • 当从没有 __slots____dict__ 属性 那个班级的永远是 可访问,因此 __slots__ 子类中的定义是 无意义的。

第六项:

  • __slots__ 的操作 声明仅限于类 它是在哪里定义的。因此, 子类将有一个 __dict__ 除非他们还定义了 __slots__ (其中必须仅包含任何名称 额外的插槽)。

在我看来,这些项目可以更好地措辞或通过代码显示,但我一直在努力解决这个问题,但仍然感到困惑。我确实了解 __slots__ 应该如何使用,并且我我试图更好地了解它们是如何工作的。

问题:

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

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

In the Python data model reference section on slots there is a list of notes on using __slots__. I am thoroughly confused by the 1st and 6th items, because they seem to be contradicting each other.

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.

Sixth item:

  • The action of a __slots__
    declaration is limited to the class
    where it is defined. As a result,
    subclasses will have a __dict__
    unless they also define __slots__
    (which must only contain names of any
    additional 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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

荒芜了季节 2024-08-20 01:58:57

正如其他人提到的,定义 __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 字节,显然“32 字节”对象 n 据称并未考虑每个实例中涉及的所有内容。您可以使用第三方扩展(例如 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优势。 它)。

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

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

如您所见,您可以在 AB 实例上设置属性 a -- AB 本身只定义了 slot b,但它继承了 A 的 slot a。重复继承的槽并不是被禁止的:

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

但确实浪费了一点内存:

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

所以确实没有理由这样做。

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. 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(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. You can do a better job with third-party extensions such as 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. 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. You can also get a microscopic speedup (partly due to better cache use for small objects with __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 disadvantage, for getting it).

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

绝不放开 2024-08-20 01:58:57
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__。它只是为了节省内存,但牺牲了一些灵活性。除非你有数以万计的对象,否则这并不重要。

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

初心未许 2024-08-20 01:58:57

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

我对第 1 条和第 6 条感到非常困惑,因为它们似乎相互矛盾。

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

不实现 __slots__ 的类的子类

我越来越意识到,尽管 Python 文档(正确地)被认为是伟大的,但它们并不完美,特别是对于该语言的较少使用的功能。我将更改 docs 如下:

当从没有 __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__ 的更多信息,请在此处查看我的答案。)

Python: How does inheritance of __slots__ in subclasses actually work?

I am thoroughly confused by the 1st and 6th items, because they seem to be contradicting each other.

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

Subclasses of classes that don't implement __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. 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__ is still meaningful for such a class. 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__.

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

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

Subclasses of classes that do implement __slots__

The action of a __slots__ declaration is limited to the class where it
is defined. As a result, subclasses will have a __dict__ unless they
also define __slots__ (which must only contain names of any additional
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. 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).

I have actually updated it to read:

The action of a __slots__ declaration is not limited to the class
where it is defined. __slots__ declared in parents are available in
child classes. However, child subclasses will get a __dict__ and
__weakref__ unless they also define __slots__ (which should only contain names of any additional 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.)

放低过去 2024-08-20 01:58:57

从您链接的答案中:

正确使用__slots__是为了节省对象的空间。而不是动态字典...

“当从没有 __slots__ 的类继承时,该类的 __dict__ 属性将始终是可访问的”,因此添加您自己的 __slots__ 无法阻止对象拥有 __dict__,并且无法节省空间。

关于 __slots__ 不被继承的说法有点迟钝。请记住,它是一个魔法属性,其行为与其他属性不同,然后重新阅读,说这个魔法槽行为不是继承的。 (这就是全部内容了。)

From the answer you linked:

The proper use of __slots__ is to save space in objects. 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.

The bit about __slots__ not being inherited is a little obtuse. 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.)

情深如许 2024-08-20 01:58:57

我的理解是这样的:

  • class X 没有 __dict__ <--------> class X 及其超类都指定了 __slots__

  • 在这种情况下,该类的实际插槽由X 及其超类的 __slots__ 声明的并集;如果此联合不是不相交的,则行为未定义(并且将成为错误)

My understanding is as follows:

  • class X has no __dict__ <-------> class X and its superclasses all have __slots__ specified

  • in this case, the actual slots of the class are comprised from the union of __slots__ declarations for X and its superclasses; the behavior is undefined (and will become an error) if this union is not disjoint

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文