Search code examples
pythonvirtualmultiple-inheritance

How to make a non-overriding method stub in Python multi-inheritance?


Imagine that you have 2 mixin classes, that each define abstract methods and implementations. Together they implement every method, but depending on the inheritance order, the empty stubs will overwrite the implementation from the other class. There's at least two ways to overcome this in most situations but I don't really like either.

  1. One could remove the abstract methods and just rely on duck typing, but then there is no clear interface definition and type hinting.
  2. One could try to break down the classes into smaller ones to get a straight line of dependency and force a specific inheritance order, but that's not always practical.

Is there a way to, for example, mark a method virtual, which prevents it from actually being added to the class, or at least prevents it from overriding an existing method of the same name?

Is there another solution I didn't think of?

Simple example:

class MixinA:
    def high_level(self):
        self.mid_level()

    def low_level(self):
        ...

    def mid_level(self):
        raise NotImplementedError


class MixinB:
    def mid_level(self):
        self.low_level()

    def low_level(self):
        raise NotImplementedError


class ChildA(MixinA, MixinB):
    pass


class ChildB(MixinB, MixinA):
    pass


for cls in (ChildA, ChildB):
    try:
        cls().high_level()
        print("success")
    except NotImplementedError:
        print("error")

Solution

  • I found a solution that satisfies my requirements, that is:

    • it describes an interface which the inheriting class needs to implement
    • does not override existing functions with abstract functions
    • is typing compatible

    Basically, I just defined the abstract functions in a separate protocol and marked the self var as that protocol.

    from typing import Protocol
    
    
    class InterfaceForA(Protocol):
        def mid_level(self): ...
    
    
    class MixinA:
        def high_level(self: InterfaceForA):
            self.mid_level()
    
        def low_level(self):
            print("success")
    
    
    class InterfaceForB(Protocol):
        def low_level(self): ...
    
    
    class MixinB:
        def mid_level(self: InterfaceForB):
            self.low_level()
    
    
    class ChildA(MixinA, MixinB):
        pass
    
    
    class ChildB(MixinB, MixinA):
        pass
    
    
    for cls in ChildA, ChildB:
        try:
            cls().high_level()
        except:
            print("error")
    

    With this code, I get success printed twice. In addition, the type hints let me know if the method implementations in the mixin are incompatible with the requirements defined in the protocols.