Python的“超级”是如何实现的? 做正确的事吗?

发布于 2024-07-14 06:57:48 字数 1262 浏览 6 评论 0原文

我正在运行Python 2.5,所以这个问题可能不适用于Python 3。当您使用多重继承创建菱形类层次结构并创建最派生类的对象时,Python 会做正确的事情(TM)。 它调用最派生类的构造函数,然后调用从左到右列出的其父类,最后调用祖父类。 我熟悉Python的MRO; 这不是我的问题。 我很好奇从 super 返回的对象实际上如何设法以正确的顺序与父类中的 super 调用进行通信。 考虑这个示例代码:

#!/usr/bin/python

class A(object):
    def __init__(self): print "A init"

class B(A):
    def __init__(self):
        print "B init"
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        super(D, self).__init__()

x = D()

该代码做了直观的事情,它打印:

D init
B init
C init
A init

但是,如果您注释掉 B 的 init 函数中对 super 的调用,则 A 和 C 的 init 函数都不会被调用。 这意味着 B 对 super 的调用在某种程度上知道 C 在整个类层次结构中的存在。 我知道 super 返回一个带有重载 get 运算符的代理对象,但是 D 的 init 定义中 super 返回的对象如何将 C 的存在传达给 B 的 init 定义中 super 返回的对象? 后续调用 super 使用的信息是否存储在对象本身上? 如果是这样,为什么不是 super 而不是 self.super?

编辑:Jekke 非常正确地指出它不是 self.super 因为 super 是类的属性,而不是类的实例。 从概念上讲这是有道理的,但实际上 super 也不是该类的属性! 您可以在解释器中通过创建两个类 A 和 B(其中 B 继承自 A)并调用 dir(B) 来测试这一点。 它没有 super__super__ 属性。

I'm running Python 2.5, so this question may not apply to Python 3. When you make a diamond class hierarchy using multiple inheritance and create an object of the derived-most class, Python does the Right Thing (TM). It calls the constructor for the derived-most class, then its parent classes as listed from left to right, then the grandparent. I'm familiar with Python's MRO; that's not my question. I'm curious how the object returned from super actually manages to communicate to calls of super in the parent classes the correct order. Consider this example code:

#!/usr/bin/python

class A(object):
    def __init__(self): print "A init"

class B(A):
    def __init__(self):
        print "B init"
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        super(D, self).__init__()

x = D()

The code does the intuitive thing, it prints:

D init
B init
C init
A init

However, if you comment out the call to super in B's init function, neither A nor C's init function is called. This means B's call to super is somehow aware of C's existence in the overall class hierarchy. I know that super returns a proxy object with an overloaded get operator, but how does the object returned by super in D's init definition communicate the existence of C to the object returned by super in B's init definition? Is the information that subsequent calls of super use stored on the object itself? If so, why isn't super instead self.super?

Edit: Jekke quite rightly pointed out that it's not self.super because super is an attribute of the class, not an instance of the class. Conceptually this makes sense, but in practice super isn't an attribute of the class either! You can test this in the interpreter by making two classes A and B, where B inherits from A, and calling dir(B). It has no super or __super__ attributes.

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

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

发布评论

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

评论(5

吃兔兔 2024-07-21 06:57:48

将您的代码更改为此,我认为它会解释一些事情(大概 super 正在查看 B 位于 __mro__ 中的位置? ):

class A(object):
    def __init__(self):
        print "A init"
        print self.__class__.__mro__

class B(A):
    def __init__(self):
        print "B init"
        print self.__class__.__mro__
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        print self.__class__.__mro__
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        print self.__class__.__mro__
        super(D, self).__init__()

x = D()

如果你运行它,你会看到:

D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

另外,值得一试 Python 的 Super 很漂亮,但你不能使用它

Change your code to this and I think it'll explain things (presumably super is looking at where, say, B is in the __mro__?):

class A(object):
    def __init__(self):
        print "A init"
        print self.__class__.__mro__

class B(A):
    def __init__(self):
        print "B init"
        print self.__class__.__mro__
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        print self.__class__.__mro__
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        print self.__class__.__mro__
        super(D, self).__init__()

x = D()

If you run it you'll see:

D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

Also it's worth checking out Python's Super is nifty, but you can't use it.

一瞬间的火花 2024-07-21 06:57:48

我在下面提供了一些链接,它们比我希望的更详细、更准确地回答了您的问题。 不过,我也会用我自己的话回答你的问题,以节省你的时间。 我将把它放在点上 -

  1. super 是一个内置函数,而不是一个属性。
  2. Python 中的每个类型(类)都有一个__mro__ 属性,用于存储该特定实例的方法解析顺序。
  3. 每次调用 super 的形式都是 super(type[, object-or-type])。 我们假设第二个属性目前是一个对象。
  4. 在超级调用的起点,对象是派生类的类型(例如 DC)。
  5. super 在指定为第一个参数的类(在本例中是 DC 之后的类)之后,在 MRO 中的类中查找匹配的方法(在您的情况下为 __init__)。
  6. 当找到匹配的方法时(例如在 BC1 类中),就会调用它。
    (这个方法应该使用 super,所以我假设它确实如此 - 请参阅Python's super is nifty but can't be use - 下面的链接)
    然后,该方法会在对象类的 MRO 中搜索 BC1 右侧的下一个方法。
  7. 重复冲洗直至找到并调用所有方法。

示例说明

 MRO: D,B,C,A,object  
  1. super(D, self).__init__() 被调用。 isinstance(self, D) =>; True
  2. 在 D 右侧的类中的 MRO 中搜索下一个方法

    B.__init__ 找到并调用


  1. B.__init__ 调用 super(B, self).__init__()

    isinstance(self, B) =>; 错误
    isinstance(self, D) =>; True

  2. 因此,MRO是相同的,但是搜索继续到B的右侧,即C,A,对象被一一搜索。 调用下一个找到的__init__

  3. 等等。

super的解释
http://www.python.org/download/releases/2.2 .3/descrintro/#合作
使用super时需要注意的事项
http://fuhm.net/super-harmful/
Python MRO 算法:
http://www.python.org/download/releases/2.3/mro/< /a>
super 的文档:
http://docs.python.org/library/functions.html
此页面的底部有一个关于超级的很好的部分:
http://docstore.mik.ua/ orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

我希望这有助于澄清它。

I have provided a bunch of links below, that answer your question in more detail and more precisely than I can ever hope to. I will however give an answer to your question in my own words as well, to save you some time. I'll put it in points -

  1. super is a builtin function, not an attribute.
  2. Every type (class) in Python has an __mro__ attribute, that stores the method resolution order of that particular instance.
  3. Each call to super is of the form super(type[, object-or-type]). Let us assume that the second attribute is an object for the moment.
  4. At the starting point of super calls, the object is of the type of the Derived class (say DC).
  5. super looks for methods that match (in your case __init__) in the classes in the MRO, after the class specified as the first argument (in this case classes after DC).
  6. When the matching method is found (say in class BC1), it is called.
    (This method should use super, so I am assuming it does - See Python's super is nifty but can't be used - link below)
    That method then causes a search in the object's class' MRO for the next method, to the right of BC1.
  7. Rinse wash repeat till all methods are found and called.

Explanation for your example

 MRO: D,B,C,A,object  
  1. super(D, self).__init__() is called. isinstance(self, D) => True
  2. Search for next method in the MRO in classes to the right of D.

    B.__init__ found and called


  1. B.__init__ calls super(B, self).__init__().

    isinstance(self, B) => False
    isinstance(self, D) => True

  2. Thus, the MRO is the same, but the search continues to the right of B i.e. C,A,object are searched one by one. The next __init__ found is called.

  3. And so on and so forth.

An explanation of super
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
Things to watch for when using super
http://fuhm.net/super-harmful/
Pythons MRO Algorithm:
http://www.python.org/download/releases/2.3/mro/
super's docs:
http://docs.python.org/library/functions.html
The bottom of this page has a nice section on super:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

I hope this helps clear it up.

败给现实 2024-07-21 06:57:48

只是猜测:

所有四个方法中的 self 都引用同一个对象,即 D 类的对象。
因此,在 B.__init__() 中,对 super(B,self) 的调用知道 self 的整个钻石血统,并且它具有从“after”B 获取方法。 在本例中,它是 C 类。

just guessing:

self in all the four methods refer to the same object, that is, of class D.
so, in B.__init__(), the call to to super(B,self) knows the whole diamond ancestry of self and it has to fetch the method from 'after' B. in this case, it's the C class.

听,心雨的声音 2024-07-21 06:57:48

super() 知道完整类层次结构。 这就是 B 的 init 内部发生的事情:

>>> super(B, self)
<super: <class 'B'>, <D object>>

这解决了核心问题,

D 的 init 定义中 super 返回的对象如何将 C 的存在传达给 B 的 init 定义中 super 返回的对象?

也就是说,在 B 的 init 定义中,selfD 的一个实例,从而传达了 C 的存在。 例如,C 可以在 type(self).__mro__ 中找到。

super() knows the full class hierarchy. This is what happens inside B's init:

>>> super(B, self)
<super: <class 'B'>, <D object>>

This resolves the central question,

how does the object returned by super in D's init definition communicate the existence of C to the object returned by super in B's init definition?

Namely, in B's init definition, self is an instance of D, and thus communicates the existence of C. For example C can be found in type(self).__mro__.

心病无药医 2024-07-21 06:57:48

Jacob 的回答展示了如何理解问题,batbrat 的回答展示了细节,hrr 的回答则开门见山。

他们没有从你的问题中涵盖(至少没有明确)的一件事是:

但是,如果注释掉 B 的 init 函数中对 super 的调用,则 A 和 C 的 init 函数都不会被调用。

要理解这一点,请将 Jacob 的代码更改为在 A 的 init 上打印堆栈,如下所示:

import traceback

class A(object):
    def __init__(self):
        print "A init"
        print self.__class__.__mro__
        traceback.print_stack()

class B(A):
    def __init__(self):
        print "B init"
        print self.__class__.__mro__
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        print self.__class__.__mro__
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        print self.__class__.__mro__
        super(D, self).__init__()

x = D()

看到 B 的行 super(B, self).__init__( ) 实际上是调用 C.__init__(),因为 C 不是 B 的基类。

D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
  File "/tmp/jacobs.py", line 31, in <module>
    x = D()
  File "/tmp/jacobs.py", line 29, in __init__
    super(D, self).__init__()
  File "/tmp/jacobs.py", line 17, in __init__
    super(B, self).__init__()
  File "/tmp/jacobs.py", line 23, in __init__
    super(C, self).__init__()
  File "/tmp/jacobs.py", line 11, in __init__
    traceback.print_stack()

发生这种情况是因为 super (B, self) 没有“调用 B 的基类版本的 __init__”。 相反,它是在 self上的 B 右侧的第一个类上调用 __init__ __mro__ 并且具有这样的属性。

因此,如果您在 B 的 init 函数中注释掉对 super 的调用,方法堆栈将在 B.__init__ 处停止,并且永远不会到达 C > 或 A

总结一下:

  • 无论引用哪个类,self始终是对实例的引用,并且其__mro____class__保持常量
  • super () 查找查找 __mro__ 上当前类右侧的类的方法。 由于 __mro__ 保持不变,因此它会作为列表而不是树或图进行搜索。

关于最后一点,请注意,MRO 算法的全名是C3 超类线性化。 也就是说,它将该结构扁平化为列表。 当不同的 super() 调用发生时,它们会有效地迭代该列表。

Jacob's answer shows how to understand the problem, while batbrat's shows the details and hrr's goes straight to the point.

One thing they do not cover (at least not explicity) from your question is this point:

However, if you comment out the call to super in B's init function, neither A nor C's init function is called.

To understand that, change Jacob's code to to print the stack on A's init, as below:

import traceback

class A(object):
    def __init__(self):
        print "A init"
        print self.__class__.__mro__
        traceback.print_stack()

class B(A):
    def __init__(self):
        print "B init"
        print self.__class__.__mro__
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C init"
        print self.__class__.__mro__
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print "D init"
        print self.__class__.__mro__
        super(D, self).__init__()

x = D()

It is a bit surprising to see that B's line super(B, self).__init__() is actually calling C.__init__(), as C is not a baseclass of B.

D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
  File "/tmp/jacobs.py", line 31, in <module>
    x = D()
  File "/tmp/jacobs.py", line 29, in __init__
    super(D, self).__init__()
  File "/tmp/jacobs.py", line 17, in __init__
    super(B, self).__init__()
  File "/tmp/jacobs.py", line 23, in __init__
    super(C, self).__init__()
  File "/tmp/jacobs.py", line 11, in __init__
    traceback.print_stack()

This happens because super (B, self) is not 'calling the B's baseclass version of __init__'. Instead, it is 'calling __init__ on the first class to the right of B that is present on self's __mro__ and that has such an attribute.

So, if you comment out the call to super in B's init function, the method stack will stop on B.__init__, and will never reach C or A.

To summarize:

  • Regardless of which class is referring to it, self is always a reference to the instance, and its __mro__ and __class__ remain constant
  • super() finds the method looking to the classes that are to the right of the current one on the __mro__. As the __mro__ remains constant, what happens is that it is searched as a list, not as a tree or a graph.

On that last point, note that the full name of the MRO's algorithm is C3 superclass linearization. That is, it flattens that structure into a list. When the different super() calls happen, they are effectivelly iterating that list.

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