【问题标题】:Question on Python super multiple inheritance关于Python超多继承的问题
【发布时间】:2020-03-09 12:50:20
【问题描述】:

我是Python新手,对超级函数和多重继承真的很困惑。

这是玩具代码:

class A():
    def __init__(self):
        print('Call class A')        
        print('Leave class A')

class C(A):
    def __init__(self):
        print('Call class C')
        A.__init__(self)
        print('Leave class C')
class D(A):
    def __init__(self):
        print('Call class D')
        #A.__init__(self)
        super(D,self).__init__()
        print('Leave class D')
class B(A):
    def __init__(self):
        print('Call class B')
        super(B,self).__init__()
        print('Leave class B')

class E(C,B,D):
    def __init__(self):
        print('Call class E')
        B.__init__(self)
        #C.__init__(self)
        #D.__init__(self)
        print('Leave class E')

那么输出是:

Call class E
Call class B
Call class D
Call class A
Leave class A
Leave class D
Leave class B
Leave class E
(<class '__main__.E'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)

其中调用了 D 类的 __init__。但是,如果我将 E 类更改为:

class E(B,C,D):
    def __init__(self):
        print('Call class E')
        B.__init__(self)
        #C.__init__(self)
        #D.__init__(self)
        print('Leave class E')

如果B、C、D的顺序改变了,那么输出是:

Call class E
Call class B
Call class C
Call class A
Leave class A
Leave class C
Leave class B
Leave class E
(<class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)

那么D类的__init__不会被调用,而是C类的,虽然类没有使用super函数。

有人知道吗: 1、在第一个代码中,为什么还要调用D类的__init__? 2、第二个代码为什么调用了C类的__init__而没有调用D类的__init__

【问题讨论】:

  • 这与PyTorch无关,这是一个纯Python问题。问题是用另一个类实例作为参数调用一个类的 init,这是没有意义的,并且是奇怪行为的原因。
  • 你必须在层次结构中一致地使用super(这就是为什么我们说super支持cooperative继承)。
  • 观看此演讲以了解有关继承和 super() 的所有信息:youtube.com/watch?v=EiOglTERPEo
  • @Dese 不,这在 Python 中很正常,而且非常有意义。事实上,这是您在super 可用之前必须 做的事情,如果您不尝试使用协作多重 继承,这完全没问题。
  • @juanpa.arrivillaga 确实我应该更明确一点:如果你也使用 super 就不要这样做

标签: python multiple-inheritance super


【解决方案1】:

未能始终使用super 意味着您无法保证 MRO 将正确行走。

在您的第一个示例中,在 E.__init__ 中对 B.__init__ 的显式调用确保永远不会调用 C.__init__,因为在 MRO 中 B 位于 C 之后。

在第二个示例中,B 恰好是E 之后的下一个类,因此(还)没有任何问题。但是在C.__init__ 中,你再次使用A.__init__(self) 而不是super(C, self).__init__(),这意味着你直接从C 跳转到A,绕过了应该是下一个的D

当设计一个使用super的类层次结构时,你必须确保每个类都使用super来保证遵循由selfruntime 类型选择的MRO,而不是在编译 时对父类方法的硬编码调用。

正确的定义是

class A:
    def __init__(self):
        print('Call class A')
        super().__init__()       
        print('Leave class A')

class C(A):
    def __init__(self):
        print('Call class C')
        super().__init__()
        print('Leave class C')
class D(A):
    def __init__(self):
        print('Call class D')
        super().__init__()
        print('Leave class D')
class B(A):
    def __init__(self):
        print('Call class B')
        super().__init__()
        print('Leave class B')

class E(C,B,D):
    def __init__(self):
        print('Call class E')
        super().__init__(self)
        print('Leave class E')

在 Python 3 中,一些编译器魔法在调用 super 时提供了“默认”参数,而没有任何参数。例如,在E.__init__ 中,super().__init___() 等价于super(C, self).__init__()

因为self 的值(以及它的类型)在整个调用链中是恒定的,所以每次使用super() 都指的是MRO 中type(self) 的下一个类。在上面的例子中,这将产生输出

Call class E
Call class C
Call class B
Call class D
Call class A
Leave class A
Leave class D
Leave class B
Leave class C
Leave class E

如果您更改E 定义中基类的顺序,MRO(以及输出)将自动更新,而无需更改任何进一步的代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-08
    • 2021-05-08
    • 1970-01-01
    • 2012-10-20
    • 1970-01-01
    • 2023-04-06
    相关资源
    最近更新 更多