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
Post a Comment