Inheritance, Polymorphism & Dunder Methods

Inheritance, Polymorphism ও ম্যাজিক মেথড

Read: ~40 min Advanced 5 practice problems Live code runner

1. Why Inheritance?

Inheritance lets a new class reuse and extend an existing class. The new class (child / subclass) automatically gets everything the old one (parent / superclass) has, and can add new attributes or override existing methods. Polymorphism is the natural consequence: code that expects a parent type also works transparently with any subclass.

Inheritance-এর মাধ্যমে একটি নতুন class অন্য class-এর সবকিছু পুনঃব্যবহার ও সম্প্রসারণ করতে পারে। Child class বাবার সব attribute ও method পায়, নতুন কিছু যোগ করতে পারে বা বিদ্যমান method override করতে পারে। Polymorphism-এর মানে একই interface-এ বিভিন্ন behaviour।

2. Single Inheritance and super()

animals.py
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "some sound"

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)        # call parent's __init__
        self.breed = breed

    def speak(self):
        return f"{self.name} ({self.breed}) says woof!"

d = Dog("Tommy", "Labrador")
print(d.speak())
print(isinstance(d, Animal))    # True
super().__init__(name) parent class-এর __init__ call করে। override করা speak method child-এর নিজস্ব।

3. Multiple Inheritance and MRO

Python allows multiple inheritance. When a method is called, Python walks the Method Resolution Order (MRO) — a linearisation of all ancestor classes — to decide which version to run. You can inspect it with ClassName.__mro__.

Python-এ multiple inheritance সম্ভব। কোন method চালাবে সেটি ঠিক করতে Python MRO (Method Resolution Order) follow করে। Class.__mro__-এ এই তালিকা দেখা যায়।
A B(A) C(A) D(B, C) MRO of D: D → B → C → A → object Figure 23.1 — Diamond inheritance ও C3 linearisation।
mro.py
class A:
    def who(self): return "A"
class B(A):
    def who(self): return "B"
class C(A):
    def who(self): return "C"
class D(B, C): pass

print(D().who())                # "B"
print([c.__name__ for c in D.__mro__])

4. Dunder (Magic) Methods

Methods with double underscores around the name hook into Python's built-in operations: len(x) calls x.__len__(), x + y calls x.__add__(y), print(x) uses x.__str__(), and so on.

নামের চারপাশে double underscore থাকা method Python-এর built-in operator বা function-এর সাথে যুক্ত হয়। len(x) → x.__len__(), x + y → x.__add__(y) ইত্যাদি।
vector.py
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

    def __len__(self):
        return int((self.x**2 + self.y**2) ** 0.5)

a = Vector(3, 4)
b = Vector(1, 2)
print(a + b)          # Vector(4, 6)
print(len(a))          # 5
print(a == Vector(3, 4))

5. Abstract Base Classes (abc)

Sometimes you want to guarantee every subclass implements a certain method. The abc module lets you mark classes as abstract.

কখনও কখনও আপনি চান যেন প্রতিটি subclass নির্দিষ্ট একটি method বাধ্যতামূলকভাবে implement করে। abc module দিয়ে এটি করা যায়।
shapes.py
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self): ...

class Square(Shape):
    def __init__(self, side): self.side = side
    def area(self): return self.side ** 2

class Circle(Shape):
    def __init__(self, r): self.r = r
    def area(self): return 3.14159 * self.r ** 2

for s in [Square(4), Circle(3)]:
    print(type(s).__name__, "area =", round(s.area(), 2))

6. Composition vs Inheritance

✅ Composition (কম্পোজিশন)

  • Object holds other objects as attributes
  • Has-a relationship
  • Loose coupling, easy to change
  • Recommended default in Python

⚠️ Inheritance (ইনহেরিটেন্স)

  • Subclass IS-A parent
  • Tight coupling — parent changes affect children
  • Deep hierarchies become painful
  • Use sparingly — 2-3 levels max
Community rule — "Favor composition over inheritance." প্রথমে composition-এ সমাধান খুঁজুন, inheritance-কে শুধু বাস্তব IS-A সম্পর্কের জন্য রাখুন।

7. Vocabulary

TermMeaningবাংলায়
SubclassClass that inherits from another.অন্য class থেকে inherit করা class।
super()Proxy to the parent class.Parent class-এর proxy।
MROMethod Resolution Order.Method-এর resolution ক্রম।
DunderDouble-underscore method hooked into built-ins.Built-in operation-এ যুক্ত double-underscore method।
ABCAbstract Base Class — unimplementable blueprint.Abstract Base Class — implement বাধ্যতামূলক করা blueprint।
PolymorphismSame call, different behaviour by type.একই call, type অনুযায়ী ভিন্ন আচরণ।

8. Practice Problems

  1. Create Vehicle and subclass Car(Vehicle) that adds wheels=4.
    Vehicle এবং তার subclass Car লিখুন যেখানে wheels=4।
    ✨ Show Answer
    ans1.py
    class Vehicle:
        def __init__(self, brand): self.brand = brand
    
    class Car(Vehicle):
        def __init__(self, brand):
            super().__init__(brand)
            self.wheels = 4
    
    c = Car("Toyota")
    print(c.brand, c.wheels)
  2. Write Money class supporting + between two Money values in the same currency.
    Money class লিখুন যেখানে একই currency-র দুইটি Money যোগ করা যাবে।
    ✨ Show Answer
    ans2.py
    class Money:
        def __init__(self, amount, currency):
            self.amount, self.currency = amount, currency
    
        def __add__(self, other):
            if self.currency != other.currency:
                raise ValueError("currency mismatch")
            return Money(self.amount + other.amount, self.currency)
    
        def __str__(self):
            return f"{self.amount} {self.currency}"
    
    print(Money(100, "BDT") + Money(50, "BDT"))
  3. Explain what super().__init__() does in one line.
    এক লাইনে ব্যাখ্যা করুন super().__init__() কী করে।
    ✨ Show Answer

    Answer: It calls the parent class's __init__ so the parent can do its own initialisation before the child adds or changes anything.

    এটি parent class-এর __init__ কল করে, যাতে parent-এর নিজস্ব initialisation সম্পন্ন হয়।

  4. Make a Playlist class that supports len() and iteration using __len__ and __iter__.
    Playlist class লিখুন যা len() ও iteration সাপোর্ট করবে।
    ✨ Show Answer
    ans4.py
    class Playlist:
        def __init__(self, songs):
            self.songs = list(songs)
        def __len__(self): return len(self.songs)
        def __iter__(self): return iter(self.songs)
    
    p = Playlist(["Runa", "James", "LRB"])
    print(len(p))
    for s in p: print(s)
  5. When should you prefer composition over inheritance? Give one concrete example.
    Composition কখন inheritance-এর চেয়ে ভালো? একটি বাস্তব উদাহরণ দিন।
    ✨ Show Answer

    Answer: When the relation is "has-a" rather than "is-a". Example: a Car has an Engine, a GPS, and four Wheels — it does not inherit from them. Composition keeps those components replaceable; inheritance would lock the design into a rigid taxonomy.

    যখন সম্পর্কটি "has-a", "is-a" নয়। উদাহরণ: একটি Car-এর একটি Engine ও GPS থাকে — তাদের থেকে inherit করে না। Composition-এ component গুলো আলাদাভাবে পরিবর্তন করা যায়।

Summary — Module 23

Subclassing reuses and extends an existing class; super() delegates to the parent. Python supports multiple inheritance via a well-defined MRO. Dunder methods plug your classes directly into Python's syntax. Use abc to enforce method contracts, and prefer composition over deep inheritance hierarchies for maintainable code.

Inheritance দিয়ে class পুনঃব্যবহার করা যায়, super() parent-এ delegate করে। Python MRO অনুযায়ী multiple inheritance সামলায়। Dunder method class-কে Python-এর built-in syntax-এ যুক্ত করে। গভীর inheritance-এর বদলে composition-কে অগ্রাধিকার দিন।

Next Module → Exception Handling — সুন্দরভাবে fail করা।