13: Multiple Inheritance

Summary

  • Single inheritance doesn’t satisfy all of the requirements of domain modeling and doesn’t allow for modifications to inheritance chains to introduce useful mixin

  • Multiple inheritance is superficially easy, but it changes the hierarchy of classes into a more general graph where each class can be inherited more than once

  • If you recast the idea of multiple inheritance asa way of modifying inheritance chains then the solution to the diamond problem is to linearize the inheritance graph to produce an inheritance chain in the form of the mro

  • The C3 algorithm can produce a linearized inheritance chain that has good properties

  • Not all cases of multiple inheritance can be linearized using C3 but these are problematic and best avoided

  • Once you understand how the mri is constructed you can start to control the way that multiple inheritance determines the mro and you can make informed choices of how to define the inheritance

  • The resulting mro allows each class to have a unique superclass and the super() function makes use of this to return a single superclass for any class in the mro

  • As the inheritance chain can vary, a given class may have a different superclass depending on which mro you are using

  • To make cooperative multiple inheritance work you have to make sure that each class calls the __init__ of its superclass and makes use of the dwindling parameter pattern to allow for the superclass to vary

Program

"""
Multiple Inheritance and MRO Demonstration

This program shows:
- Why single inheritance is sometimes not enough
- How multiple inheritance creates a graph (not a simple chain)
- How Python resolves this using MRO (Method Resolution Order)
- How super() works with the MRO
- How to properly design cooperative multiple inheritance using
  the "dwindling parameter pattern"
"""

# -------------------------------
# BASE CLASS
# -------------------------------

class Base:
    def __init__(self, **kwargs):
        """
        Base class initializer.
        Uses **kwargs to support cooperative multiple inheritance.
        """
        print("Base initialized")
        super().__init__(**kwargs)


# -------------------------------
# MIXIN CLASSES
# -------------------------------

class LoggerMixin:
    def __init__(self, log_enabled=True, **kwargs):
        """
        A mixin that adds logging capability.
        Demonstrates modifying the inheritance chain.
        """
        self.log_enabled = log_enabled
        print("LoggerMixin initialized")
        super().__init__(**kwargs)


class DatabaseMixin:
    def __init__(self, db_connected=True, **kwargs):
        """
        Another mixin that adds database functionality.
        """
        self.db_connected = db_connected
        print("DatabaseMixin initialized")
        super().__init__(**kwargs)


# -------------------------------
# MAIN CLASS USING MULTIPLE INHERITANCE
# -------------------------------

class Application(LoggerMixin, DatabaseMixin, Base):
    def __init__(self, app_name, **kwargs):
        """
        Final class that combines multiple behaviors.

        Demonstrates:
        - Multiple inheritance
        - MRO linearization (C3 algorithm)
        - Cooperative initialization using super()
        """
        self.app_name = app_name
        print("Application initialized")
        super().__init__(**kwargs)


# -------------------------------
# RUN EXAMPLE
# -------------------------------

if __name__ == "__main__":
    print("MRO of Application:")
    for cls in Application.__mro__:
        print("  ", cls.__name__)

    print("\nCreating Application instance:\n")

    app = Application(
        app_name="MyApp",
        log_enabled=True,
        db_connected=False
    )

Program Output

 MRO of Application:
Application
LoggerMixin
DatabaseMixin
Base
object

 Creating Application instance:

 Application initialized
 LoggerMixin initialized
 DatabaseMixin initialized
 Base initialized