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:
Son()does not define its own__init__, so it inheritsFather.__init__.Father.__init__explicitly callssuper().__init__(), which means it’s callingMother.__init__()becauseMotheris the next in line in the MRO afterFather.- But
Mother.__init__expects onlyfirstandlast(and passes them toPerson.__init__along withgender="f"). - The error arises because both
FatherandMothercallsuper().__init__()directly toPerson, without cooperating in a true cooperative multiple inheritance model.
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 Python, for multiple inheritance to work, all classes involved must use
super()cooperatively and consistently, i.e., they all should:- Call
super().__init__(), notParentClass.__init__(). - Accept arbitrary arguments like
*argsand**kwargs, and pass them along.
- Call
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:
- Use
super().__init__() - Accept
*args, **kwargsin__init__ - 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
- ❌ The original code fails due to improper handling of
super()in a multiple inheritance scenario. - ✅ Fix it by using
*args, **kwargsand cooperativesuper()calls throughout the hierarchy.
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
- Use cooperative
super()for multiple inheritance. - Each
__init__should accept*args, **kwargsand pass them tosuper().__init__()to keep the chain alive. - Python MRO ensures that every class is only initialized once and in the correct order.
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