a-walk-in-python

A walk in Python

View on GitHub

Here is a chat with ChatGPT 4o that will shine a light on multiple inheritance and MRO: (pay attention to the note at the end, there is an error in the AI’s fixed version of the code)

Why does the following code throw an error when creating an instance of class Son?:

class Person():
    """Basic person class"""
    def __init__(self, first="", last="", gender="n/a"):
        self.first_name = first
        self.last_name = last
        self.gender = gender


class Father(Person):
    """Person of masculine gender that has descendants"""
    def __init__(self, first="", last=""):
        super().__init__(first=first, last=last, gender="m")
        self.has_children = True

class Mother(Person):
    """Person of femenine gender that has descendants"""
    def __init__(self, first="", last=""):
        super().__init__(first=first, last=last, gender="f")
        self.has_children = True


class Son(Father, Mother):
    """Naive example of a person that IS mother and father!"""
    pass

ChatGPTs answer:

Great question — this code throws an error when trying to create an instance of the Son class due to Python’s Method Resolution Order (MRO) and the use of multiple inheritance.

Let’s walk through the issue step-by-step:


⚠️ The Problem

You’re using multiple inheritance like this:

class Son(Father, Mother):

This means Python will try to construct Son by calling the constructors (__init__ methods) of its base classes in MRO order, which is:

Son  Father  Mother  Person

Now, here’s the critical part:

This causes one base class to skip the other’s initialization, or to be initialized multiple times incorrectly. Eventually, you get:

TypeError: object.__init__() takes exactly one argument (the instance to initialize)

This happens because super() reaches object.__init__() at the end of the MRO chain, and it’s being called with arguments it doesn’t accept.


🧠 Why It Really Fails (The Technical Details)

In your code, Father and Mother each define an __init__ that assumes it’s the only one calling Person.__init__, which breaks the cooperative chain.


✅ Fixing It (Cooperative Multiple Inheritance)

To make this work properly, all classes involved must:

  1. Use super().__init__()
  2. Accept *args, **kwargs in __init__
  3. Pass unused arguments forward with super()

Here’s a corrected version:

class Person():
    def __init__(self, first="", last="", gender="n/a", **kwargs):
        super().__init__(**kwargs)
        self.first_name = first
        self.last_name = last
        self.gender = gender

class Father(Person):
    def __init__(self, first="", last="", **kwargs):
        super().__init__(first=first, last=last, gender="m", **kwargs)
        self.has_children = True

class Mother(Person):
    def __init__(self, first="", last="", **kwargs):
        super().__init__(first=first, last=last, gender="f", **kwargs)
        self.has_children = True

class Son(Father, Mother):
    pass

Now, super() in each class will follow the MRO, and all constructors will be called in a clean and cooperative way.


🔁 MRO Check

You can inspect the method resolution order with:

print(Son.__mro__)

Output:

(<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Mother'>, <class '__main__.Person'>, <class 'object'>)

Summary

Want a visual breakdown of the MRO or how super() travels through the classes?

Me:

Yes, please, I would like the visual breakdown

ChatGPT:

Awesome! Here’s a visual breakdown of the Method Resolution Order (MRO) and how super() travels through the classes in your Son example.


Awesome! Here’s a visual breakdown of the Method Resolution Order (MRO) and how super() travels through the classes in your Son example.


📦 Class Hierarchy

         Person
        /      \
   Father      Mother
        \      /
           Son

🧭 MRO (Method Resolution Order) for Son

When you create Son(), Python determines the order in which constructors (__init__) should be called using the C3 linearization algorithm. The MRO for Son is:

Son  Father  Mother  Person  object

This is what you’ll see when running:

print(Son.__mro__)

🔁 super() Chain — Step by Step

Let’s assume you call:

s = Son(first="Alex", last="Smith")

If the classes are written cooperatively with super(), here’s how the flow works:


✅ Step-by-step init chain (with cooperative super()):

1. Son() uses Father.__init__()
2. Father.__init__() calls super().__init__() → goes to Mother.__init__()
3. Mother.__init__() calls super().__init__() → goes to Person.__init__()
4. Person.__init__() calls super().__init__() → goes to object.__init__()
5. object.__init__() does nothing (default base class)

Each class sets up its own data and passes the rest along. No class is skipped.


🚨 What Happens Without Cooperative super() (i.e., the original code)

Your original Father.__init__() and Mother.__init__() each call super().__init__() assuming they’re directly inheriting from Person. This breaks the chain.

So in this situation:

s = Son(first="Alex", last="Smith")

Here’s what happens:

1. Son() uses Father.__init__()
2. Father.__init__() calls super().__init__() → goes to Mother.__init__()
3. Mother.__init__() calls super().__init__() → goes to Person.__init__()
4. Person.__init__() calls super().__init__() → goes to object.__init__()
   BUT Person passes parameters like `first`, `last`, `gender` to object.__init__()
   ❌ object.__init__() only accepts `self` → 💥 TypeError

🧪 Visual Chain Flow (Correct Version)

Here’s a clean diagram showing the call flow for __init__:

Son()
  ↓
Father.__init__()
  ↓
super() →
Mother.__init__()
  ↓
super() →
Person.__init__()
  ↓
super() →
object.__init__() ✅

Each class contributes to the initialization and passes unused arguments along the chain.


🧙 Summary

Let me know if you’d like a graphical image of this hierarchy or want to test MRO dynamically!

Important note:

There is an error in ChatGPTs answer, the corrected code in the ✅ Fixing It (Cooperative Multiple Inheritance) section doesn´t work. That’s because in the base class Person the call to super().__init__ can´t pass the kwarg parameters, because that class doesn’t inherit from any other class (at least explicitly, it implicitly inherits from object): the constructor is not expecting those parameters and fails.

This can also serve as a reminder that ChatGPT’s answers need to be checked and validated. Don´t put blind trust on the tool -at least not yet :D