Skip to content

Method Resolution Order

The Method Resolution Order (MRO) defines how Python resolves methods in inheritance hierarchies, especially with multiple inheritance.


Why MRO Exists

1. Ambiguity Problem

class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

class C(A):
    def method(self):
        return "C"

class D(B, C):
    pass

d = D()
d.method()  # Which method?

2. Diamond Problem

    A
   / \
  B   C
   \ /
    D

When D inherits from both B and C, which inherit from A, how should methods resolve?

3. Deterministic Order

MRO provides a consistent, predictable resolution order.


Inspecting MRO

1. Using .mro()

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

print(D.mro())
# [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]

2. Using __mro__

print(D.__mro__)
# Same output as .mro()

3. Reading the Order

Methods are searched left to right through this list.


MRO Principles

1. Child First

The class itself is always first in MRO.

2. Left-to-Right

Parents are searched in declaration order.

class D(B, C):  # B before C
    pass

3. Parents Before Ancestors

A parent appears before its own parents.

# D → B → C → A
# B appears before A
# C appears before A

C3 Linearization

1. Algorithm Used

Python uses C3 linearization to compute MRO.

2. Three Properties

  • Preserves local precedence order
  • Maintains parent MRO
  • Ensures monotonicity

3. Consistency

Guarantees a consistent resolution order or raises an error.


MRO and super()

1. super() Follows MRO

class A:
    def method(self):
        print("A")

class B(A):
    def method(self):
        super().method()  # calls next in MRO
        print("B")

2. Cooperative Calls

class D(B, C):
    def method(self):
        super().method()  # calls B.method
        # B.method calls super() → C.method
        # C.method calls super() → A.method

3. Each Class Once

Even in diamond inheritance, each class is called exactly once.


Invalid MRO

1. Inconsistent Hierarchy

class X: pass
class Y: pass

class A(X, Y): pass
class B(Y, X): pass  # reversed order

class C(A, B): pass  # Error!

2. TypeError Raised

TypeError: Cannot create a consistent method resolution order (MRO)

3. Fix the Design

Rearrange inheritance to be consistent.


Practical Advice

1. Single Inheritance

Prefer single inheritance when possible—simpler MRO.

2. Understand Your MRO

Always check .mro() in complex hierarchies.

3. Use super() Consistently

Design all classes to work cooperatively.

4. Avoid Deep Hierarchies

Keep inheritance shallow for maintainability.


Key Takeaways

  • MRO defines method lookup order.
  • Python uses C3 linearization algorithm.
  • .mro() reveals the resolution order.
  • super() follows MRO, not just parent.
  • Invalid hierarchies raise TypeError.

Runnable Example: diamond_problem.py

"""
03_diamond_problem.py

INTERMEDIATE LEVEL: The Diamond Problem and Python's Solution

This file explains the classic "diamond problem" in multiple inheritance
and demonstrates how Python's MRO (using C3 linearization) solves it elegantly.

The diamond problem occurs when a class inherits from two classes that both
inherit from a common base class, creating a diamond-shaped inheritance graph.

Learning Objectives:
- Understand what the diamond problem is
- See how Python prevents the diamond problem
- Learn about duplicate base class prevention
- Understand why C3 linearization is important
"""

# ============================================================================
# SECTION 1: What is the Diamond Problem?
# ============================================================================

if __name__ == "__main__":

    """
    The Diamond Problem:

            A
           / \
          B   C
           \ /
            D

    Class D inherits from B and C
    Classes B and C both inherit from A

    Questions that arise:
    1. Which version of A's methods does D get?
    2. Is A's __init__ called once or twice?
    3. In what order are methods searched?
    4. How do we prevent conflicts and ambiguity?

    Python solves this using C3 Linearization algorithm, which ensures:
    - Each class appears only ONCE in the MRO
    - The MRO is consistent and predictable
    - Child classes come before parent classes
    - Parent order is preserved from the class definition
    """


    # ============================================================================
    # SECTION 2: Classic Diamond Problem Example
    # ============================================================================

    class A:
        """
        Base class at the top of the diamond.
        This is the common ancestor.
        """

        def __init__(self):
            print("A.__init__ called")
            self.value_a = "From A"

        def method(self):
            return "Method from A"

        def show_mro(self):
            """Helper method to show where method is called from."""
            return "show_mro from A"


    class B(A):
        """
        Left branch of the diamond.
        Inherits from A.
        """

        def __init__(self):
            print("B.__init__ called")
            super().__init__()  # Call A.__init__
            self.value_b = "From B"

        def method(self):
            return "Method from B"

        def method_b(self):
            return "Specific to B"


    class C(A):
        """
        Right branch of the diamond.
        Also inherits from A.
        """

        def __init__(self):
            print("C.__init__ called")
            super().__init__()  # Call A.__init__
            self.value_c = "From C"

        def method(self):
            return "Method from C"

        def method_c(self):
            return "Specific to C"


    class D(B, C):
        """
        Bottom of the diamond.
        Inherits from both B and C, which both inherit from A.

        This creates the diamond:
               A
              / \
             B   C
              \ /
               D

        Python's MRO: D -> B -> C -> A -> object

        Notice: A appears only ONCE at the end, not twice!
        This is Python's solution to the diamond problem.
        """

        def __init__(self):
            print("D.__init__ called")
            super().__init__()  # This will call the next class in MRO
            self.value_d = "From D"


    print("="*70)
    print("CLASSIC DIAMOND PROBLEM")
    print("="*70)

    # Create an instance of D
    print("\nCreating instance of D:")
    d = D()
    # Notice: A.__init__ is called only ONCE, not twice!

    # View the MRO
    print("\nMRO for class D:")
    for i, cls in enumerate(D.__mro__, 1):
        print(f"{i}. {cls.__name__}")
    # Output:
    # 1. D
    # 2. B
    # 3. C
    # 4. A      <- A appears only once!
    # 5. object

    # Method resolution follows this MRO
    print(f"\nCalling d.method(): {d.method()}")  # Finds in B (first in MRO after D)
    print(f"Calling d.method_b(): {d.method_b()}")  # Finds in B
    print(f"Calling d.method_c(): {d.method_c()}")  # Finds in C
    print(f"Calling d.show_mro(): {d.show_mro()}")  # Finds in A


    # ============================================================================
    # SECTION 3: Understanding super() in Diamond Inheritance
    # ============================================================================

    """
    The key to Python's diamond problem solution is super().

    super() doesn't just call the parent class - it calls the NEXT class in MRO!

    This ensures:
    1. Each class's __init__ is called exactly once
    2. The MRO is followed correctly
    3. No duplicate initialization
    """


    class Animal:
        """Base animal class."""

        def __init__(self, name):
            print(f"Animal.__init__ called for {name}")
            self.name = name


    class Mammal(Animal):
        """Mammal branch."""

        def __init__(self, name, warm_blooded=True):
            print(f"Mammal.__init__ called for {name}")
            super().__init__(name)  # Calls next in MRO, not necessarily Animal!
            self.warm_blooded = warm_blooded


    class Winged(Animal):
        """Winged branch."""

        def __init__(self, name, can_fly=True):
            print(f"Winged.__init__ called for {name}")
            super().__init__(name)  # Calls next in MRO
            self.can_fly = can_fly


    class Bat(Mammal, Winged):
        """
        Bat is both a mammal and winged.

        Diamond structure:
               Animal
               /    \
          Mammal  Winged
               \    /
                Bat

        MRO: Bat -> Mammal -> Winged -> Animal -> object
        """

        def __init__(self, name):
            print(f"Bat.__init__ called for {name}")
            # super().__init__ will call Mammal.__init__ (next in MRO)
            # Mammal's super().__init__ will call Winged.__init__ (next in MRO)
            # Winged's super().__init__ will call Animal.__init__ (next in MRO)
            super().__init__(name, warm_blooded=True)
            self.nocturnal = True


    print("\n" + "="*70)
    print("SUPER() IN DIAMOND INHERITANCE")
    print("="*70)

    print("\nCreating a Bat instance:")
    print("Watch the order of __init__ calls:\n")
    bruce = Bat("Bruce")

    print("\nMRO for Bat:")
    for i, cls in enumerate(Bat.__mro__, 1):
        print(f"{i}. {cls.__name__}")

    print(f"\nBat attributes:")
    print(f"  Name: {bruce.name}")
    print(f"  Warm-blooded: {bruce.warm_blooded}")
    print(f"  Nocturnal: {bruce.nocturnal}")
    # Note: can_fly is not set because we didn't pass it to Mammal.__init__


    # ============================================================================
    # SECTION 4: Proper Diamond Pattern with Keyword Arguments
    # ============================================================================

    """
    To properly handle diamond inheritance with different parameters,
    use **kwargs to pass arguments through the MRO chain.
    """


    class Shape:
        """Base shape class."""

        def __init__(self, color="black", **kwargs):
            print(f"Shape.__init__ called with color={color}")
            super().__init__(**kwargs)  # Pass remaining kwargs up the chain
            self.color = color


    class Rectangle(Shape):
        """Rectangle shape."""

        def __init__(self, width=0, height=0, **kwargs):
            print(f"Rectangle.__init__ called with width={width}, height={height}")
            super().__init__(**kwargs)
            self.width = width
            self.height = height

        def area(self):
            return self.width * self.height


    class Border(Shape):
        """Border mixin."""

        def __init__(self, border_width=1, **kwargs):
            print(f"Border.__init__ called with border_width={border_width}")
            super().__init__(**kwargs)
            self.border_width = border_width


    class BorderedRectangle(Rectangle, Border):
        """
        Rectangle with a border.

        Diamond structure:
               Shape
               /    \
        Rectangle  Border
               \    /
         BorderedRectangle

        MRO: BorderedRectangle -> Rectangle -> Border -> Shape -> object
        """

        def __init__(self, width=0, height=0, border_width=1, color="black"):
            print(f"BorderedRectangle.__init__ called")
            super().__init__(
                width=width,
                height=height,
                border_width=border_width,
                color=color
            )

        def total_area(self):
            """Calculate total area including border."""
            inner_area = self.width * self.height
            border_area = (self.width + 2*self.border_width) * (self.height + 2*self.border_width)
            return border_area


    print("\n" + "="*70)
    print("PROPER DIAMOND PATTERN WITH KWARGS")
    print("="*70)

    print("\nCreating a BorderedRectangle:")
    print("Watch how arguments flow through the MRO:\n")
    rect = BorderedRectangle(width=10, height=5, border_width=2, color="red")

    print("\nMRO for BorderedRectangle:")
    for i, cls in enumerate(BorderedRectangle.__mro__, 1):
        print(f"{i}. {cls.__name__}")

    print(f"\nBorderedRectangle attributes:")
    print(f"  Width: {rect.width}")
    print(f"  Height: {rect.height}")
    print(f"  Border width: {rect.border_width}")
    print(f"  Color: {rect.color}")
    print(f"  Inner area: {rect.area()}")
    print(f"  Total area: {rect.total_area()}")


    # ============================================================================
    # SECTION 5: Invalid MRO - When Diamond Goes Wrong
    # ============================================================================

    """
    Not all inheritance patterns are valid. Python will raise TypeError
    if the MRO cannot be computed consistently using C3 linearization.
    """


    class X:
        pass


    class Y:
        pass


    class A(X, Y):
        """A inherits from X, then Y."""
        pass


    class B(Y, X):
        """B inherits from Y, then X - opposite order!"""
        pass


    # This would create an inconsistent MRO and raise TypeError:
    # class C(A, B):
    #     """
    #     C inherits from A and B.
    #     A says: X before Y
    #     B says: Y before X
    #     This is inconsistent - Python cannot resolve this!
    #     """
    #     pass

    print("\n" + "="*70)
    print("INVALID MRO EXAMPLE")
    print("="*70)

    print("\nTrying to create an inconsistent MRO:")
    print("class A(X, Y): pass  # A says X before Y")
    print("class B(Y, X): pass  # B says Y before X")
    print("class C(A, B): pass  # C cannot resolve: X before Y or Y before X?")
    print("\nThis would raise: TypeError: Cannot create a consistent MRO")


    # Uncomment to see the actual error:
    # try:
    #     class C(A, B):
    #         pass
    # except TypeError as e:
    #     print(f"\nActual error: {e}")


    # ============================================================================
    # SECTION 6: Real-World Diamond Example - GUI Components
    # ============================================================================

    """
    A practical example: GUI components that share common functionality.
    """


    class Widget:
        """Base widget class."""

        def __init__(self, id="widget", **kwargs):
            super().__init__(**kwargs)
            print(f"Widget.__init__: creating {id}")
            self.id = id
            self.visible = True

        def show(self):
            self.visible = True
            return f"{self.id} is now visible"

        def hide(self):
            self.visible = False
            return f"{self.id} is now hidden"


    class Clickable(Widget):
        """Mixin for clickable widgets."""

        def __init__(self, on_click=None, **kwargs):
            super().__init__(**kwargs)
            print(f"Clickable.__init__: setting up click handler")
            self.on_click = on_click
            self.click_count = 0

        def click(self):
            self.click_count += 1
            result = f"Clicked {self.id} (count: {self.click_count})"
            if self.on_click:
                self.on_click()
            return result


    class Hoverable(Widget):
        """Mixin for hoverable widgets."""

        def __init__(self, on_hover=None, **kwargs):
            super().__init__(**kwargs)
            print(f"Hoverable.__init__: setting up hover handler")
            self.on_hover = on_hover
            self.is_hovered = False

        def hover(self):
            self.is_hovered = True
            result = f"Hovering over {self.id}"
            if self.on_hover:
                self.on_hover()
            return result


    class Button(Clickable, Hoverable):
        """
        Button widget that is both clickable and hoverable.

        Diamond structure:
               Widget
               /    \
        Clickable  Hoverable
               \    /
               Button

        MRO: Button -> Clickable -> Hoverable -> Widget -> object
        """

        def __init__(self, id="button", label="Click me", **kwargs):
            super().__init__(id=id, **kwargs)
            print(f"Button.__init__: creating button with label '{label}'")
            self.label = label

        def render(self):
            """Render the button."""
            state = "visible" if self.visible else "hidden"
            hover = " (hovered)" if self.is_hovered else ""
            return f"[Button '{self.label}' - {state}{hover} - clicks: {self.click_count}]"


    print("\n" + "="*70)
    print("REAL-WORLD EXAMPLE: GUI BUTTON")
    print("="*70)

    print("\nCreating a Button:")
    print("Initialization order follows MRO:\n")

    def click_handler():
        print("  -> Click handler executed!")

    def hover_handler():
        print("  -> Hover handler executed!")

    submit_btn = Button(
        id="submit_button",
        label="Submit Form",
        on_click=click_handler,
        on_hover=hover_handler
    )

    print("\nMRO for Button:")
    for i, cls in enumerate(Button.__mro__, 1):
        print(f"{i}. {cls.__name__}")

    print(f"\nButton operations:")
    print(submit_btn.render())
    print(submit_btn.click())
    print(submit_btn.hover())
    print(submit_btn.render())
    print(submit_btn.click())
    print(submit_btn.render())


    # ============================================================================
    # SECTION 7: Key Takeaways
    # ============================================================================

    """
    KEY POINTS ABOUT THE DIAMOND PROBLEM:

    1. Diamond Problem Structure:
               A
              / \
             B   C
              \ /
               D
       D inherits from B and C, both inherit from A

    2. Python's Solution:
       - C3 Linearization algorithm
       - Each class appears ONLY ONCE in MRO
       - Preserves order of parent classes
       - Ensures consistent, predictable method resolution

    3. super() is the Key:
       - super() calls the NEXT class in MRO, not the parent
       - This prevents duplicate initialization
       - Always use super() in cooperative inheritance

    4. Use **kwargs:
       - Pass parameters through the MRO chain
       - Each class takes what it needs
       - Passes the rest to super().__init__()

    5. Invalid MROs:
       - Some inheritance patterns are inconsistent
       - Python raises TypeError for invalid MRO
       - Different parent orders can create conflicts

    6. Practical Uses:
       - Mixin classes (Clickable, Hoverable, etc.)
       - Shared functionality across hierarchies
       - Plugin architectures
       - Framework base classes

    7. Best Practices:
       - Document your MRO intentions
       - Use super() consistently
       - Design parent classes to be "cooperative"
       - Test complex hierarchies thoroughly
       - Consider composition for very complex scenarios
    """

    print("\n" + "="*70)
    print("END OF DIAMOND PROBLEM TUTORIAL")
    print("="*70)
    print("\nNext: Learn about super() and its relationship with MRO!")

Runnable Example: mro_diamond_problem.py

"""
TUTORIAL: Method Resolution Order (MRO) - Understanding the Diamond Problem
===========================================================================

In this tutorial, you'll learn how Python's Method Resolution Order (MRO)
handles the "diamond problem" in multiple inheritance.

The Diamond Problem:
  A class (Leaf) inherits from two classes (A and B) which both inherit from
  a common parent (Root). When you call a method on Leaf, which version runs?
  This is the diamond problem.

Python uses C3 Linearization to solve this:
  1. Each class is visited in a specific order
  2. Each class appears only once in the order
  3. A class never appears before its parents
  4. The order respects the order in which parents are listed

Key Insight: super() uses MRO to call the next method in the chain.
This allows cooperative multiple inheritance where methods can be properly
delegated up the inheritance hierarchy.

In this example, we see:
  - How print(ClassName.__mro__) shows the resolution order
  - How super() uses MRO to call methods
  - Which methods get called from which classes
  - Why the order matters
"""


# ============ Example 1: Defining the Diamond Inheritance Hierarchy ============

if __name__ == "__main__":
    print("=" * 70)
    print("EXAMPLE 1: Diamond inheritance - Root > A, B > Leaf")
    print("=" * 70)

    class Root:
        """The common parent at the top of the diamond.

        Both A and B inherit from Root. Methods here are called when
        neither A, B, nor Leaf override them.
        """
        def ping(self):
            print(f'{self}.ping() in Root')

        def pong(self):
            print(f'{self}.pong() in Root')

        def __repr__(self):
            cls_name = type(self).__name__
            return f'<instance of {cls_name}>'


    class A(Root):
        """Left side of the diamond.

        Inherits from Root. Both ping() and pong() use super() to call
        the parent implementation, continuing the method chain.
        """
        def ping(self):
            print(f'{self}.ping() in A')
            super().ping()

        def pong(self):
            print(f'{self}.pong() in A')
            super().pong()


    class B(Root):
        """Right side of the diamond.

        Inherits from Root. ping() calls super(), but pong() does NOT.
        This demonstrates how different classes can handle the chain differently.
        """
        def ping(self):
            print(f'{self}.ping() in B')
            super().ping()

        def pong(self):
            print(f'{self}.pong() in B')
            # Note: No super().pong() call - chain stops here


    class Leaf(A, B):
        """Bottom of the diamond - inherits from both A and B.

        Leaf specifies inheritance order: A first, then B.
        This order affects the MRO.
        """
        def ping(self):
            print(f'{self}.ping() in Leaf')
            super().ping()


    print(f"\nDiamond hierarchy defined:")
    print(f"         Root")
    print(f"        /    \\")
    print(f"       A      B")
    print(f"        \\    /")
    print(f"         Leaf")
    print(f"\nClass definitions:")
    print(f"  class Root: ping(), pong()")
    print(f"  class A(Root): ping() with super(), pong() with super()")
    print(f"  class B(Root): ping() with super(), pong() WITHOUT super()")
    print(f"  class Leaf(A, B): ping() with super()")


    # ============ Example 2: Understanding the Method Resolution Order ============
    print("\n" + "=" * 70)
    print("EXAMPLE 2: Method Resolution Order (MRO)")
    print("=" * 70)

    print(f"\nLeaf.__mro__ shows the order methods are looked up:")
    mro = Leaf.__mro__
    print(f"  {Leaf.__mro__}")

    print(f"\nBreaking it down:")
    for i, cls in enumerate(mro):
        print(f"  {i}. {cls}")

    print(f"\nWhat this means:")
    print(f"  - When Leaf.method() is called, Python looks in Leaf first")
    print(f"  - Then A (from Leaf(A, B) left-to-right order)")
    print(f"  - Then B")
    print(f"  - Then Root")
    print(f"  - Finally object (implicit parent of all classes)")

    print(f"\nWhy this order?")
    print(f"  1. Leaf inherits from A first, so A has priority")
    print(f"  2. A and B both inherit from Root, but Root comes after both")
    print(f"  3. This respects the inheritance hierarchy")
    print(f"  4. C3 Linearization ensures consistency")


    # ============ Example 3: Calling ping() - Method Chain with super() ============
    print("\n" + "=" * 70)
    print("EXAMPLE 3: Calling ping() - tracing the super() chain")
    print("=" * 70)

    leaf1 = Leaf()
    print(f"\nleaf1 = Leaf()")
    print(f"leaf1.ping()  # Trace the method chain:")
    print()
    leaf1.ping()

    print(f"\nExplanation of ping() call chain:")
    print(f"  1. Leaf.ping() called")
    print(f"     - Prints: '<instance of Leaf>.ping() in Leaf'")
    print(f"     - Calls: super().ping()")
    print(f"     - super() uses MRO: next class after Leaf is A")
    print(f"\n  2. A.ping() called via super()")
    print(f"     - Prints: '<instance of Leaf>.ping() in A'")
    print(f"     - Calls: super().ping()")
    print(f"     - super() uses MRO: next class after A is B")
    print(f"\n  3. B.ping() called via super()")
    print(f"     - Prints: '<instance of Leaf>.ping() in B'")
    print(f"     - Calls: super().ping()")
    print(f"     - super() uses MRO: next class after B is Root")
    print(f"\n  4. Root.ping() called via super()")
    print(f"     - Prints: '<instance of Leaf>.ping() in Root'")
    print(f"     - No super() call, chain ends")


    # ============ Example 4: Calling pong() - Super Chain Stops Early ============
    print("\n" + "=" * 70)
    print("EXAMPLE 4: Calling pong() - where the super() chain stops")
    print("=" * 70)

    print(f"\nleaf1.pong()  # Trace the pong() chain:")
    print()
    leaf1.pong()

    print(f"\nExplanation of pong() call chain:")
    print(f"  1. Leaf doesn't override pong(), so MRO looks further")
    print(f"     - Next class with pong() is A")
    print(f"\n  2. A.pong() called")
    print(f"     - Prints: '<instance of Leaf>.pong() in A'")
    print(f"     - Calls: super().pong()")
    print(f"     - super() uses MRO: next class is B")
    print(f"\n  3. B.pong() called via super()")
    print(f"     - Prints: '<instance of Leaf>.pong() in B'")
    print(f"     - NOTE: No super().pong() call!")
    print(f"     - Chain STOPS here, Root.pong() is never called!")

    print(f"\nKey insight:")
    print(f"  - A calls super(), so chain continues to B")
    print(f"  - B does NOT call super(), so chain stops")
    print(f"  - Root.pong() is never reached")
    print(f"  - This is a design choice - B can intentionally end the chain")


    # ============ Example 5: MRO Affects Behavior ============
    print("\n" + "=" * 70)
    print("EXAMPLE 5: MRO affects which implementation runs")
    print("=" * 70)

    print(f"\nWhat if we changed Leaf(A, B) to Leaf(B, A)?")
    print(f"  class Leaf(B, A):  # B first, then A")
    print(f"      def ping(self):")
    print(f"          print(f'{{self}}.ping() in Leaf')")
    print(f"          super().ping()")

    class LeafReversed(B, A):
        """Same as Leaf but with inheritance order reversed."""
        def ping(self):
            print(f'{self}.ping() in LeafReversed')
            super().ping()

    print(f"\nLeafReversed.__mro__:")
    print(f"  {LeafReversed.__mro__}")

    print(f"\nNotice: B comes before A now!")
    print(f"  (because Leaf(B, A) puts B first)")

    leaf2 = LeafReversed()
    print(f"\nleaf2 = LeafReversed()")
    print(f"leaf2.ping():")
    print()
    leaf2.ping()


    # ============ Example 6: Multiple Inheritance with Shared Methods ============
    print("\n" + "=" * 70)
    print("EXAMPLE 6: Why super() is better than explicit parent calls")
    print("=" * 70)

    print(f"\nWithout super() (explicit parent calls):")
    print(f"  class A(Root):")
    print(f"      def ping(self):")
    print(f"          print('ping in A')")
    print(f"          Root.ping(self)  # EXPLICIT call")

    print(f"\nProblem: If we change inheritance (Leaf inherits from B, A),")
    print(f"A still calls Root directly, skipping B in the chain!")

    print(f"\nWith super() (uses MRO):")
    print(f"  class A(Root):")
    print(f"      def ping(self):")
    print(f"          print('ping in A')")
    print(f"          super().ping()  # Uses MRO")

    print(f"\nAdvantage: super() automatically uses the MRO,")
    print(f"so it works correctly regardless of inheritance order!")

    print(f"\nIn Leaf(A, B): super() in A calls B (correct chain)")
    print(f"In LeafReversed(B, A): super() in A calls Root (still correct)")


    # ============ Example 7: Real-World MRO Example ============
    print("\n" + "=" * 70)
    print("EXAMPLE 7: Practical example - Mixin classes with MRO")
    print("=" * 70)

    class TimestampMixin:
        """A mixin that adds timestamp capability."""
        def get_info(self):
            return f'{super().get_info()} [timestamped]'

    class SerializableMixin:
        """A mixin that adds serialization capability."""
        def get_info(self):
            return f'{super().get_info()} [serializable]'

    class DataObject:
        """Base class for data objects."""
        def get_info(self):
            return 'DataObject'

    class Document(TimestampMixin, SerializableMixin, DataObject):
        """Document with multiple capabilities via mixins."""
        pass

    print(f"\nDocument.__mro__:")
    print(f"  {Document.__mro__}")

    doc = Document()
    print(f"\ndoc = Document()")
    print(f"doc.get_info() = '{doc.get_info()}'")

    print(f"\nMethod chain:")
    print(f"  1. Document.get_info() → not found, use MRO")
    print(f"  2. TimestampMixin.get_info() → adds '[timestamped]'")
    print(f"     → calls super().get_info()")
    print(f"  3. SerializableMixin.get_info() → adds '[serializable]'")
    print(f"     → calls super().get_info()")
    print(f"  4. DataObject.get_info() → returns 'DataObject'")


    # ============ Example 8: Summary - MRO Best Practices ============
    print("\n" + "=" * 70)
    print("EXAMPLE 8: Summary - MRO Best Practices")
    print("=" * 70)

    print(f"\nRules for MRO (C3 Linearization):")
    print(f"  1. Child classes come before parents")
    print(f"  2. Parents are listed in inheritance order")
    print(f"  3. Each class appears only once")

    print(f"\nUsing super() effectively:")
    print(f"  1. Always use super() instead of explicit parent calls")
    print(f"  2. super() respects MRO and works with multiple inheritance")
    print(f"  3. Mixins should always use super() to continue the chain")
    print(f"  4. Remember: super() doesn't call the parent - it calls the NEXT class in MRO")

    print(f"\nDebugging MRO:")
    print(f"  1. Print ClassName.__mro__ to see the resolution order")
    print(f"  2. Use print() statements to trace method chains")
    print(f"  3. Remember that inheritance order matters!")

    print(f"\nCommon mistakes:")
    print(f"  1. Forgetting super() in a cooperative hierarchy")
    print(f"  2. Not understanding that inheritance order affects MRO")
    print(f"  3. Calling parent explicitly instead of using super()")

    print(f"\n" + "=" * 70)