# 211. Multiple Inheritance Pitfalls

#### **🔹 1. Diamond Problem – Ambiguity in Method Resolution Order (MRO)**

```python
class A:
    def greet(self):
        print("Hello from A")

class B(A):
    pass

class C(A):
    def greet(self):
        print("Hello from C")

class D(B, C):
    pass

d = D()
d.greet()  # Hello from C
print(D.mro())  # [D, B, C, A, object]
```

🔍 **Issue:**

* `D` inherits from both `B` and `C`, but `C` overrides `greet`.
* **Method Resolution Order (MRO)** determines that `C`'s method is called.

***

#### **🔹 2. MRO Conflicts – Inconsistent Hierarchy**

```python
class A: pass
class B(A): pass
class C: pass
class D(B, C): pass  # Works
class E(C, B): pass  # Works
# class F(D, E): pass  # ❌ TypeError: Cannot create consistent method resolution order (MRO)
```

🔍 **Issue:**

* The MRO becomes **inconsistent** when `D` and `E` have different base class orders.

***

#### **🔹 3. Super() Calls in Multiple Inheritance**

```python
class A:
    def greet(self):
        print("A")

class B(A):
    def greet(self):
        super().greet()
        print("B")

class C(A):
    def greet(self):
        super().greet()
        print("C")

class D(B, C):
    def greet(self):
        super().greet()
        print("D")

d = D()
d.greet()
```

🔍 **Issue:**

* Calls `super()` correctly based on **MRO**:

```
A
C
B
D
```

* This works if **all** classes use `super()` correctly.

***

#### **🔹 4. Using `super()` Incorrectly**

```python
class A:
    def __init__(self):
        print("A initialized")

class B(A):
    def __init__(self):
        print("B initialized")
        super().__init__()  # Calls A

class C(A):
    def __init__(self):
        print("C initialized")
        super().__init__()  # Calls A again!

class D(B, C):
    def __init__(self):
        print("D initialized")
        super().__init__()

d = D()
```

🔍 **Issue:**

```
D initialized
B initialized
C initialized
A initialized
```

* **`A.__init__()` gets called twice!**
* Fix this by using `super()` **only if all classes cooperate**.

***

#### **🔹 5. Attribute Name Conflicts**

```python
class A:
    def __init__(self):
        self.value = 10

class B:
    def __init__(self):
        self.value = 20

class C(A, B):
    pass

c = C()
print(c.value)  # 10 (from A)
```

🔍 **Issue:**

* Both `A` and `B` define `value`.
* **MRO picks `A`’s version**, potentially leading to bugs.

***

#### **🔹 6. Overriding Methods in Unexpected Ways**

```python
class A:
    def work(self):
        print("A working")

class B(A):
    def work(self):
        print("B working")

class C(A):
    def work(self):
        print("C working")

class D(B, C):
    pass

d = D()
d.work()  # B working (due to MRO)
```

🔍 **Issue:**

* The method in `C` is **ignored** because `B` comes first in `D(B, C)`.

***

#### **🔹 7. Calling Parent Methods Explicitly**

```python
class A:
    def show(self):
        print("A")

class B(A):
    def show(self):
        A.show(self)
        print("B")

class C(A):
    def show(self):
        A.show(self)
        print("C")

class D(B, C):
    def show(self):
        B.show(self)
        C.show(self)
        print("D")

d = D()
d.show()
```

🔍 **Issue:**

```
A
B
A
C
D
```

* **Bad practice**: Calls `A.show()` twice.
* **Better to use `super()`** to avoid redundant calls.

***

#### **🔹 8. Using `isinstance` with Multiple Base Classes**

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

d = D()
print(isinstance(d, B))  # True
print(isinstance(d, C))  # True
print(isinstance(d, A))  # True
```

🔍 **Issue:**

* `isinstance(d, A)` is **True**, even though `D` never explicitly inherits from `A`.

***

#### **🔹 9. Multiple Base Classes with `super()`**

```python
class A:
    def __init__(self):
        print("A init")

class B(A):
    def __init__(self):
        print("B init")
        super().__init__()

class C(A):
    def __init__(self):
        print("C init")
        super().__init__()

class D(B, C):
    def __init__(self):
        print("D init")
        super().__init__()

d = D()
```

🔍 **Correct MRO prevents double initialization:**

```
D init
B init
C init
A init
```

* **Ensure all classes use `super()` correctly** to prevent duplicate calls.

***

#### **🔹 10. Avoiding Multiple Inheritance with Composition**

```python
class Engine:
    def start(self):
        print("Engine starting...")

class Wheels:
    def rotate(self):
        print("Wheels rotating...")

class Car:
    def __init__(self):
        self.engine = Engine()
        self.wheels = Wheels()

    def drive(self):
        self.engine.start()
        self.wheels.rotate()
        print("Car moving...")

car = Car()
car.drive()
```

🔍 **Why this works better?**

* **Composition over inheritance** avoids MRO issues.
* **More maintainable & modular** than multiple inheritance.

***

#### **🚀 Summary**

| **Pitfall**                    | **Issue**                      | **Solution**                           |
| ------------------------------ | ------------------------------ | -------------------------------------- |
| Diamond Problem                | MRO ambiguity                  | Use `super()` properly                 |
| MRO Conflicts                  | Inconsistent ordering          | Follow a clear class hierarchy         |
| Super Calls                    | Wrong method calls             | Ensure all classes use `super()`       |
| Attribute Conflicts            | Overwritten attributes         | Use unique attribute names             |
| Method Overriding              | Unexpected behavior            | Check `mro()` before overriding        |
| Explicit Parent Calls          | Repeating method calls         | Use `super()` instead                  |
| `isinstance` Issues            | Inheriting unintended behavior | Avoid unnecessary multiple inheritance |
| Multiple `__init__` Calls      | Calls parent multiple times    | Use `super()` correctly                |
| Super in Multiple Base Classes | Wrong `super()` order          | Use cooperative multiple inheritance   |
| Alternative: Composition       | Complex MRO issues             | Prefer composition over inheritance    |

***

**🚀 Best Practices**

✅ Prefer **single inheritance** unless multiple inheritance is absolutely needed.\
✅ Use **MRO (`print(Class.mro())`)** to check method resolution order.\
✅ Always **use `super()`** to avoid redundant method calls.\
✅ **Consider composition** as an alternative.
