Python OOP Part 2: Inheritance, Polymorphism and Dunder Methods

Part 8 of the Python for Everyone track. Last updated: September 2026.

Inheritance in Python is simpler than in Java — no interfaces, no abstract boilerplate, just classes building on classes. Combined with dunder methods, your custom classes can behave exactly like built-ins.

Inheritance and super()

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

    def speak(self):
        return "..."

class Dog(Animal):                 # Dog inherits from Animal
    def __init__(self, name, breed):
        super().__init__(name)     # call the parent constructor
        self.breed = breed

    def speak(self):               # override the parent method
        return f"{self.name} says woof!"

d = Dog("Rex", "Labrador")
print(d.speak())                   # Rex says woof!
print(isinstance(d, Animal))       # True — a Dog IS an Animal

Polymorphism = duck typing

class Cat(Animal):
    def speak(self):
        return f"{self.name} says meow!"

def make_it_speak(animal):
    print(animal.speak())   # works for ANY object with a speak() method

make_it_speak(Dog("Rex", "Lab"))  # Rex says woof!
make_it_speak(Cat("Tom"))         # Tom says meow!

No interfaces required: "if it quacks like a duck, it's a duck." Python cares about behavior, not declared types.

Dunder methods: make your classes Pythonic

class Playlist:
    def __init__(self):
        self.songs = []

    def add(self, song):
        self.songs.append(song)

    def __len__(self):              # len(playlist)
        return len(self.songs)

    def __getitem__(self, i):       # playlist[0], and enables for-loops!
        return self.songs[i]

    def __contains__(self, song):   # "song" in playlist
        return song in self.songs

    def __eq__(self, other):        # playlist1 == playlist2
        return self.songs == other.songs

pl = Playlist()
pl.add("Imagine")
print(len(pl))          # 1
print(pl[0])            # Imagine
print("Imagine" in pl)  # True
for song in pl:         # works because of __getitem__!
    print(song)

The most useful dunders to know: __len__, __getitem__, __contains__, __eq__, __iter__, __call__.

@classmethod vs @staticmethod

class Date:
    def __init__(self, y, m, d):
        self.y, self.m, self.d = y, m, d

    @classmethod
    def from_string(cls, s):          # alternative constructor — receives the CLASS
        y, m, d = map(int, s.split("-"))
        return cls(y, m, d)

    @staticmethod
    def is_leap(year):                # plain utility — no self, no cls
        return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

d = Date.from_string("2026-09-28")
print(Date.is_leap(2024))  # True

@classmethod is the idiomatic "alternative constructor" pattern — you'll see from_json, from_dict everywhere in real code.

Key takeaways

  • class Dog(Animal) inherits; super().__init__() calls the parent constructor.
  • Polymorphism via duck typing — behavior over declared types.
  • Dunder methods (__len__, __getitem__…) make custom classes feel built-in.
  • @classmethod for alternative constructors; @staticmethod for namespaced utilities.

Next in this series: Python Modules, Packages and pip Explained.

Comments

Popular posts from this blog

Java Banking Finance Services and Insurance (BFSI) domain interview questions

JSP Servlet Interview Questions For Freshers Series 1

Java program to check even or odd number