用多重继承调用父类__init__,正确的方法是什么?

问题:用多重继承调用父类__init__,正确的方法是什么?

假设我有多个继承方案:

class A(object):
    # code for A here

class B(object):
    # code for B here

class C(A, B):
    def __init__(self):
        # What's the right code to write here to ensure 
        # A.__init__ and B.__init__ get called?

有编写的两个典型方法C__init__

  1. (老式) ParentClass.__init__(self)
  2. (较新的样式) super(DerivedClass, self).__init__()

但是,在任何一种情况下,如果父类(AB没有遵循相同的约定,则代码将无法正常工作(某些代码可能会丢失或多次调用)。

那么又是什么正确的方法呢?说“保持一致,遵循一个或另一个”很容易,但是如果AB来自第三方图书馆,那又如何呢?有没有一种方法可以确保所有父类构造函数都被调用(以正确的顺序,并且只能调用一次)?

编辑:看看我的意思,如果我这样做:

class A(object):
    def __init__(self):
        print("Entering A")
        super(A, self).__init__()
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super(B, self).__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        A.__init__(self)
        B.__init__(self)
        print("Leaving C")

然后我得到:

Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C

请注意,Binit会被调用两次。如果我做:

class A(object):
    def __init__(self):
        print("Entering A")
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super(B, self).__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        super(C, self).__init__()
        print("Leaving C")

然后我得到:

Entering C
Entering A
Leaving A
Leaving C

请注意,B永远不会调用init。因此,似乎除非我知道/控制我从(AB)继承的类的初始化,否则我无法对正在编写的类(C)做出安全选择。

Say I have a multiple inheritance scenario:

class A(object):
    # code for A here

class B(object):
    # code for B here

class C(A, B):
    def __init__(self):
        # What's the right code to write here to ensure 
        # A.__init__ and B.__init__ get called?

There’s two typical approaches to writing C‘s __init__:

  1. (old-style) ParentClass.__init__(self)
  2. (newer-style) super(DerivedClass, self).__init__()

However, in either case, if the parent classes (A and B) don’t follow the same convention, then the code will not work correctly (some may be missed, or get called multiple times).

So what’s the correct way again? It’s easy to say “just be consistent, follow one or the other”, but if A or B are from a 3rd party library, what then? Is there an approach that can ensure that all parent class constructors get called (and in the correct order, and only once)?

Edit: to see what I mean, if I do:

class A(object):
    def __init__(self):
        print("Entering A")
        super(A, self).__init__()
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super(B, self).__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        A.__init__(self)
        B.__init__(self)
        print("Leaving C")

Then I get:

Entering C
Entering A
Entering B
Leaving B
Leaving A
Entering B
Leaving B
Leaving C

Note that B‘s init gets called twice. If I do:

class A(object):
    def __init__(self):
        print("Entering A")
        print("Leaving A")

class B(object):
    def __init__(self):
        print("Entering B")
        super(B, self).__init__()
        print("Leaving B")

class C(A, B):
    def __init__(self):
        print("Entering C")
        super(C, self).__init__()
        print("Leaving C")

Then I get:

Entering C
Entering A
Leaving A
Leaving C

Note that B‘s init never gets called. So it seems that unless I know/control the init’s of the classes I inherit from (A and B) I cannot make a safe choice for the class I’m writing (C).


回答 0

两种方式都可以正常工作。使用该方法super()可为子类带来更大的灵活性。

在直接呼叫方式中,C.__init__可以同时呼叫A.__init__B.__init__

使用时super(),需要将类设计为在其中C调用的协作式多重继承super,这将调用A的代码,该代码还将super调用B的代码。请参阅http://rhettinger.wordpress.com/2011/05/26/super-considered-super,以详细了解可以使用进行的操作super

[回答问题,稍后编辑]

因此,似乎除非我知道/控制我从(A和B)继承的类的初始化,否则我无法对我正在编写的类(C)做出安全的选择。

参考的文章显示了如何通过在A和周围添加包装器类来处理这种情况B。标题为“如何合并非合作类”的部分提供了一个可行的示例。

可能希望多重继承更容易,让您轻松组成Car和Airplane类来获得FlyingCar,但现实情况是,单独设计的组件通常需要适配器或包装器,然后才能像我们希望的那样无缝地组装在一起:-)

另一个想法:如果您对使用多重继承来编写功能不满意,则可以使用composition来完全控制在哪些情况下调用哪种方法。

Both ways work fine. The approach using super() leads to greater flexibility for subclasses.

In the direct call approach, C.__init__ can call both A.__init__ and B.__init__.

When using super(), the classes need to be designed for cooperative multiple inheritance where C calls super, which invokes A‘s code which will also call super which invokes B‘s code. See http://rhettinger.wordpress.com/2011/05/26/super-considered-super for more detail on what can be done with super.

[Response question as later edited]

So it seems that unless I know/control the init’s of the classes I inherit from (A and B) I cannot make a safe choice for the class I’m writing (C).

The referenced article shows how to handle this situation by adding a wrapper class around A and B. There is a worked-out example in the section titled “How to Incorporate a Non-cooperative Class”.

One might wish that multiple inheritance were easier, letting you effortlessly compose Car and Airplane classes to get a FlyingCar, but the reality is that separately designed components often need adapters or wrappers before fitting together as seamlessly as we would like :-)

One other thought: if you’re unhappy with composing functionality using multiple inheritance, you can use composition for complete control over which methods get called on which occasions.


回答 1

您问题的答案取决于一个非常重要的方面:您的基类是否设计用于多重继承?

有3种不同的方案:

  1. 基类是不相关的独立类。

    如果您的基类是能够独立运行的独立实体,并且彼此之间不认识,则它们不是为多重继承设计的。例:

    class Foo:
        def __init__(self):
            self.foo = 'foo'
    
    class Bar:
        def __init__(self, bar):
            self.bar = bar
    

    重要:请注意,既不打电话Foo也不Bar打电话super().__init__()!这就是为什么您的代码无法正常工作的原因。由于Diamond继承在python中的工作方式,因此object不应调用基类为的类super().__init__()。如您所知,这样做会破坏多重继承,因为您最终将调用另一个类的__init__而不是object.__init__()免责声明:避免super().__init__()object-subclasses中是我个人的建议,绝不是python社区中达成一致的共识。有些人更喜欢super在每个类中使用,认为如果该类的行为不像您通常可以编写一个适配器您期望的。)

    这也意味着您永远不应编写从其继承object且没有__init__方法的类。完全不定义__init__方法与调用具有相同的效果super().__init__()。如果您的类直接继承自object,请确保添加一个空的构造函数,如下所示:

    class Base(object):
        def __init__(self):
            pass
    

    无论如何,在这种情况下,您将必须手动调用每个父构造函数。有两种方法可以做到这一点:

    • 不带 super

      class FooBar(Foo, Bar):
          def __init__(self, bar='bar'):
              Foo.__init__(self)  # explicit calls without super
              Bar.__init__(self, bar)
      
    • super

      class FooBar(Foo, Bar):
          def __init__(self, bar='bar'):
              super().__init__()  # this calls all constructors up to Foo
              super(Foo, self).__init__(bar)  # this calls all constructors after Foo up
                                              # to Bar
      

    这两种方法各有其优点和缺点。如果你使用super,你的类将支持依赖注入。另一方面,容易出错。例如,如果你改变的顺序FooBar(像class FooBar(Bar, Foo)),你就必须更新super到匹配的电话。没有super您,不必担心这一点,并且代码更具可读性。

  2. 类之一是mixin。

    混入是,这是一个一流的设计与多重继承使用。这意味着我们不必手动调用两个父构造函数,因为mixin会自动为我们调用第二个构造函数。由于这次只需要调用一个构造函数,因此super可以避免对父类的名称进行硬编码。

    例:

    class FooMixin:
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)  # forwards all unused arguments
            self.foo = 'foo'
    
    class Bar:
        def __init__(self, bar):
            self.bar = bar
    
    class FooBar(FooMixin, Bar):
        def __init__(self, bar='bar'):
            super().__init__(bar)  # a single call is enough to invoke
                                   # all parent constructors
    
            # NOTE: `FooMixin.__init__(self, bar)` would also work, but isn't
            # recommended because we don't want to hard-code the parent class.
    

    这里的重要细节是:

    • mixin调用super().__init__()并通过它接收的任何参数。
    • 子类首先从mixin继承:class FooBar(FooMixin, Bar)。如果基类的顺序错误,则将永远不会调用mixin的构造函数。
  3. 所有基类均设计用于协作继承。

    专为合作继承而设计的类非常类似于mixin:它们将所有未使用的参数传递给下一类。和以前一样,我们只需要调用即可super().__init__(),所有父级构造函数都将被链调用。

    例:

    class CoopFoo:
        def __init__(self, **kwargs):
            super().__init__(**kwargs)  # forwards all unused arguments
            self.foo = 'foo'
    
    class CoopBar:
        def __init__(self, bar, **kwargs):
            super().__init__(**kwargs)  # forwards all unused arguments
            self.bar = bar
    
    class CoopFooBar(CoopFoo, CoopBar):
        def __init__(self, bar='bar'):
            super().__init__(bar=bar)  # pass all arguments on as keyword
                                       # arguments to avoid problems with
                                       # positional arguments and the order
                                       # of the parent classes
    

    在这种情况下,父类的顺序无关紧要。我们CoopBar最好还是从头继承,而代码仍然可以正常工作。但这是真的,因为所有参数都作为关键字参数传递。使用位置参数将很容易弄错参数的顺序,因此,协作类习惯于仅接受关键字参数。

    这也是我前面提到的规则的一个exceptions:CoopFooCoopBar都继承自object,但它们仍然调用super().__init__()。如果没有,则不会有合作继承。

底线:正确的实现取决于您从其继承的类。

构造函数是类的公共接口的一部分。如果该类被设计为混合或协作继承,则必须将其记录下来。如果文档中未提及任何内容,则可以安全地假定该类不是为协作多重继承设计的。

The answer to your question depends on one very important aspect: Are your base classes designed for multiple inheritance?

There are 3 different scenarios:

  1. The base classes are unrelated, standalone classes.

    If your base classes are separate entities that are capable of functioning independently and they don’t know each other, they’re not designed for multiple inheritance. Example:

    class Foo:
        def __init__(self):
            self.foo = 'foo'
    
    class Bar:
        def __init__(self, bar):
            self.bar = bar
    

    Important: Notice that neither Foo nor Bar calls super().__init__()! This is why your code didn’t work correctly. Because of the way diamond inheritance works in python, classes whose base class is object should not call super().__init__(). As you’ve noticed, doing so would break multiple inheritance because you end up calling another class’s __init__ rather than object.__init__(). (Disclaimer: Avoiding super().__init__() in object-subclasses is my personal recommendation and by no means an agreed-upon consensus in the python community. Some people prefer to use super in every class, arguing that you can always write an adapter if the class doesn’t behave as you expect.)

    This also means that you should never write a class that inherits from object and doesn’t have an __init__ method. Not defining a __init__ method at all has the same effect as calling super().__init__(). If your class inherits directly from object, make sure to add an empty constructor like so:

    class Base(object):
        def __init__(self):
            pass
    

    Anyway, in this situation, you will have to call each parent constructor manually. There are two ways to do this:

    • Without super

      class FooBar(Foo, Bar):
          def __init__(self, bar='bar'):
              Foo.__init__(self)  # explicit calls without super
              Bar.__init__(self, bar)
      
    • With super

      class FooBar(Foo, Bar):
          def __init__(self, bar='bar'):
              super().__init__()  # this calls all constructors up to Foo
              super(Foo, self).__init__(bar)  # this calls all constructors after Foo up
                                              # to Bar
      

    Each of these two methods has its own advantages and disadvantages. If you use super, your class will support dependency injection. On the other hand, it’s easier to make mistakes. For example if you change the order of Foo and Bar (like class FooBar(Bar, Foo)), you’d have to update the super calls to match. Without super you don’t have to worry about this, and the code is much more readable.

  2. One of the classes is a mixin.

    A mixin is a class that’s designed to be used with multiple inheritance. This means we don’t have to call both parent constructors manually, because the mixin will automatically call the 2nd constructor for us. Since we only have to call a single constructor this time, we can do so with super to avoid having to hard-code the parent class’s name.

    Example:

    class FooMixin:
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)  # forwards all unused arguments
            self.foo = 'foo'
    
    class Bar:
        def __init__(self, bar):
            self.bar = bar
    
    class FooBar(FooMixin, Bar):
        def __init__(self, bar='bar'):
            super().__init__(bar)  # a single call is enough to invoke
                                   # all parent constructors
    
            # NOTE: `FooMixin.__init__(self, bar)` would also work, but isn't
            # recommended because we don't want to hard-code the parent class.
    

    The important details here are:

    • The mixin calls super().__init__() and passes through any arguments it receives.
    • The subclass inherits from the mixin first: class FooBar(FooMixin, Bar). If the order of the base classes is wrong, the mixin’s constructor will never be called.
  3. All base classes are designed for cooperative inheritance.

    Classes designed for cooperative inheritance are a lot like mixins: They pass through all unused arguments to the next class. Like before, we just have to call super().__init__() and all parent constructors will be chain-called.

    Example:

    class CoopFoo:
        def __init__(self, **kwargs):
            super().__init__(**kwargs)  # forwards all unused arguments
            self.foo = 'foo'
    
    class CoopBar:
        def __init__(self, bar, **kwargs):
            super().__init__(**kwargs)  # forwards all unused arguments
            self.bar = bar
    
    class CoopFooBar(CoopFoo, CoopBar):
        def __init__(self, bar='bar'):
            super().__init__(bar=bar)  # pass all arguments on as keyword
                                       # arguments to avoid problems with
                                       # positional arguments and the order
                                       # of the parent classes
    

    In this case, the order of the parent classes doesn’t matter. We might as well inherit from CoopBar first, and the code would still work the same. But that’s only true because all arguments are passed as keyword arguments. Using positional arguments would make it easy to get the order of the arguments wrong, so it’s customary for cooperative classes to accept only keyword arguments.

    This is also an exception to the rule I mentioned earlier: Both CoopFoo and CoopBar inherit from object, but they still call super().__init__(). If they didn’t, there would be no cooperative inheritance.

Bottom line: The correct implementation depends on the classes you’re inheriting from.

The constructor is part of a class’s public interface. If the class is designed as a mixin or for cooperative inheritance, that must be documented. If the docs don’t mention anything of the sort, it’s safe to assume that the class isn’t designed for cooperative multiple inheritance.


回答 2

这两种方法(“新风格”或“旧式”),将工作,如果你有过的源代码控制AB。否则,可能需要使用适配器类。

可访问的源代码:正确使用“新样式”

class A(object):
    def __init__(self):
        print("-> A")
        super(A, self).__init__()
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        super(B, self).__init__()
        print("<- B")

class C(A, B):
    def __init__(self):
        print("-> C")
        # Use super here, instead of explicit calls to __init__
        super(C, self).__init__()
        print("<- C")
>>> C()
-> C
-> A
-> B
<- B
<- A
<- C

在此,方法解析顺序(MRO)规定以下内容:

  • C(A, B)A首先决定,然后B。MRO是C -> A -> B -> object
  • super(A, self).__init__()沿始于的MRO链继续C.__init__进行B.__init__
  • super(B, self).__init__()沿始于的MRO链继续C.__init__进行object.__init__

您可以说这种情况是为多重继承而设计的

可访问的源代码:正确使用“旧样式”

class A(object):
    def __init__(self):
        print("-> A")
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        # Don't use super here.
        print("<- B")

class C(A, B):
    def __init__(self):
        print("-> C")
        A.__init__(self)
        B.__init__(self)
        print("<- C")
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

在此,MRO无关紧要,因为A.__init__B.__init__被显式调用。class C(B, A):也会一样工作。

尽管这种情况不是像以前的样式那样“设计”为新样式的多重继承,但多重继承仍然是可能的。


现在,如果AB是从第三方库-即你有过的源代码没有控制AB?简短的答案:您必须设计一个实现必要super调用的适配器类,然后使用一个空类来定义MRO(请参阅Raymond Hettinger上的文章super -尤其是“如何合并非合作类”一节)。

第三方家长:A未实施superB确实

class A(object):
    def __init__(self):
        print("-> A")
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        super(B, self).__init__()
        print("<- B")

class Adapter(object):
    def __init__(self):
        print("-> C")
        A.__init__(self)
        super(Adapter, self).__init__()
        print("<- C")

class C(Adapter, B):
    pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

Adapter实现super是为了C定义MRO,该MRO在super(Adapter, self).__init__()执行时起作用。

如果反过来呢?

第三方父母:A工具superB才不是

class A(object):
    def __init__(self):
        print("-> A")
        super(A, self).__init__()
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        print("<- B")

class Adapter(object):
    def __init__(self):
        print("-> C")
        super(Adapter, self).__init__()
        B.__init__(self)
        print("<- C")

class C(Adapter, A):
    pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

此处的模式相同,除了执行顺序已切换Adapter.__init__super先呼叫,然后再进行显式呼叫。请注意,带有第三方父母的每种情况都需要一个唯一的适配器类。

因此,似乎除非我知道/控制我从(AB)继承的类的初始化,否则我无法对正在编写的类(C)做出安全选择。

虽然你可以处理,你没有的情况下,控制的源代码A,并B通过使用适配器类,这是事实,你必须知道在init怎样的父类实现super(如果有的话),以这样做。

Either approach (“new style” or “old style”) will work if you have control over the source code for A and B. Otherwise, use of an adapter class might be necessary.

Source code accessible: Correct use of “new style”

class A(object):
    def __init__(self):
        print("-> A")
        super(A, self).__init__()
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        super(B, self).__init__()
        print("<- B")

class C(A, B):
    def __init__(self):
        print("-> C")
        # Use super here, instead of explicit calls to __init__
        super(C, self).__init__()
        print("<- C")
>>> C()
-> C
-> A
-> B
<- B
<- A
<- C

Here, method resolution order (MRO) dictates the following:

  • C(A, B) dictates A first, then B. MRO is C -> A -> B -> object.
  • super(A, self).__init__() continues along the MRO chain initiated in C.__init__ to B.__init__.
  • super(B, self).__init__() continues along the MRO chain initiated in C.__init__ to object.__init__.

You could say that this case is designed for multiple inheritance.

Source code accessible: Correct use of “old style”

class A(object):
    def __init__(self):
        print("-> A")
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        # Don't use super here.
        print("<- B")

class C(A, B):
    def __init__(self):
        print("-> C")
        A.__init__(self)
        B.__init__(self)
        print("<- C")
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

Here, MRO does not matter, since A.__init__ and B.__init__ are called explicitly. class C(B, A): would work just as well.

Although this case is not “designed” for multiple inheritance in the new style as the previous one was, multiple inheritance is still possible.


Now, what if A and B are from a third party library – i.e., you have no control over the source code for A and B? The short answer: You must design an adapter class that implements the necessary super calls, then use an empty class to define the MRO (see Raymond Hettinger’s article on super – especially the section, “How to Incorporate a Non-cooperative Class”).

Third-party parents: A does not implement super; B does

class A(object):
    def __init__(self):
        print("-> A")
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        super(B, self).__init__()
        print("<- B")

class Adapter(object):
    def __init__(self):
        print("-> C")
        A.__init__(self)
        super(Adapter, self).__init__()
        print("<- C")

class C(Adapter, B):
    pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

Class Adapter implements super so that C can define the MRO, which comes into play when super(Adapter, self).__init__() is executed.

And what if it’s the other way around?

Third-party parents: A implements super; B does not

class A(object):
    def __init__(self):
        print("-> A")
        super(A, self).__init__()
        print("<- A")

class B(object):
    def __init__(self):
        print("-> B")
        print("<- B")

class Adapter(object):
    def __init__(self):
        print("-> C")
        super(Adapter, self).__init__()
        B.__init__(self)
        print("<- C")

class C(Adapter, A):
    pass
>>> C()
-> C
-> A
<- A
-> B
<- B
<- C

Same pattern here, except the order of execution is switched in Adapter.__init__; super call first, then explicit call. Notice that each case with third-party parents requires a unique adapter class.

So it seems that unless I know/control the init’s of the classes I inherit from (A and B) I cannot make a safe choice for the class I’m writing (C).

Although you can handle the cases where you don’t control the source code of A and B by using an adapter class, it is true that you must know how the init’s of the parent classes implement super (if at all) in order to do so.


回答 3

正如雷蒙德(Raymond)在回答中所说的那样,直接调用A.__init__B.__init__可以正常工作,并且您的代码易于阅读。

但是,它不使用C和这些类之间的继承链接。利用该链接可为您提供更多的一致性,并使最终的重构更加容易且不易出错。如何执行此操作的示例:

class C(A, B):
    def __init__(self):
        print("entering c")
        for base_class in C.__bases__:  # (A, B)
             base_class.__init__(self)
        print("leaving c")

As Raymond said in his answer, a direct call to A.__init__ and B.__init__ works fine, and your code would be readable.

However, it does not use the inheritance link between C and those classes. Exploiting that link gives you more consistancy and make eventual refactorings easier and less error-prone. An example of how to do that:

class C(A, B):
    def __init__(self):
        print("entering c")
        for base_class in C.__bases__:  # (A, B)
             base_class.__init__(self)
        print("leaving c")

回答 4

本文有助于解释协作式多重继承:

http://www.artima.com/weblogs/viewpost.jsp?thread=281127

它提到了有用的方法mro(),可向您显示方法解析顺序。在你的第二个例子,当你调用superA,该super呼叫继续在MRO。顺序中的下一个类是B,这就是为什么Binit首次被调用的原因。

这是来自python官方站点的更多技术文章:

http://www.python.org/download/releases/2.3/mro/

This article helps to explain cooperative multiple inheritance:

http://www.artima.com/weblogs/viewpost.jsp?thread=281127

It mentions the useful method mro() that shows you the method resolution order. In your 2nd example, where you call super in A, the super call continues on in MRO. The next class in the order is B, this is why B‘s init is called the first time.

Here’s a more technical article from the official python site:

http://www.python.org/download/releases/2.3/mro/


回答 5

如果要从第三方库中繁衍子类类,则不会,没有盲目的方法来调用__init__实际上起作用的基类方法(或任何其他方法),而不管基类的编程方式如何。

super使编写旨在协作实现方法的类成为复杂的多重继承树的一部分成为可能,而类继承者不必知道。但是无法使用它正确地从可能使用或可能不使用的任意类中继承super

本质上,一个类是设计为使用super基类还是直接调用基类来进行子类化,是属于该类“公共接口”一部分的属性,因此应进行记录。如果您以库作者所期望的方式使用第三方库,并且库具有合理的文档,则通常会告诉您需要做什么来对特定的事物进行子类化。如果不是,那么您必须查看要子类化的类的源代码,并查看其基类调用约定是什么。如果你是从一个或多个第三方库的方式,该库作者结合多个类想到,那么它可能无法始终如一地调用超类的方法在所有; 如果类A是使用的层次结构的一部分,super而类B是不使用super的层次结构的一部分,则不能保证这两种选择都不会起作用。您将必须找出一种适用于每个特定案例的策略。

If you are multiply sub-classing classes from third party libraries, then no, there is no blind approach to calling the base class __init__ methods (or any other methods) that actually works regardless of how the base classes are programmed.

super makes it possible to write classes designed to cooperatively implement methods as part of complex multiple inheritance trees which need not be known to the class author. But there’s no way to use it to correctly inherit from arbitrary classes that may or may not use super.

Essentially, whether a class is designed to be sub-classed using super or with direct calls to the base class is a property which is part of the class’ “public interface”, and it should be documented as such. If you’re using third-party libraries in the way that the library author expected and the library has reasonable documentation, it would normally tell you what you are required to do to subclass particular things. If not, then you’ll have to look at the source code for the classes you’re sub-classing and see what their base-class-invocation convention is. If you’re combining multiple classes from one or more third-party libraries in a way that the library authors didn’t expect, then it may not be possible to consistently invoke super-class methods at all; if class A is part of a hierarchy using super and class B is part of a hierarchy that doesn’t use super, then neither option is guaranteed to work. You’ll have to figure out a strategy that happens to work for each particular case.