Python: Polymorphism and Magic Methods
Last updated: 2026-09-22
The last two lessons covered the "form" of classes and inheritance. This lesson covers the "spirit." Polymorphism lets objects of different classes be called in the same way — you don't need to know their specific class, just that they have the required method. Magic methods make custom classes behave as naturally as built-in types.
1. What is Polymorphism
Polymorphism means "many forms" — the same interface, different implementations:
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Duck:
def speak(self):
return "Quack!"
# Polymorphism: regardless of class, as long as it has a speak() method
animals = [Dog(), Cat(), Duck()]
for animal in animals:
print(animal.speak())
Output:
Woof!
Meow!
Quack!
Tip: Duck Typing: "If it walks like a duck and quacks like a duck, it's a duck." Python doesn't care about an object's type — only whether it has the method you need. This is one of Python's most flexible designs.
▶ Example: Shape Area Calculation (Difficulty: Star-Star)
▶ Try it YourselfOutput:
Area: 12.00
Area: 78.54
Area: 24.00
2. Magic Methods: Making Classes More "Pythonic"
Magic methods are special methods with double underscore prefixes and suffixes — __init__, __str__, __len__, etc. They make custom classes behave like built-in types.
(1) __str__ and __repr__: Friendlier Printing
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
"""User-friendly string representation"""
return f"{self.name} ({self.age} years old)"
def __repr__(self):
"""Developer-friendly string representation"""
return f"Person('{self.name}', {self.age})"
p = Person("Zhang San", 25)
print(p) # Zhang San (25 years old) — calls __str__
print(repr(p)) # Person('Zhang San', 25) — calls __repr__
(2) __len__: Making Objects Support len()
class Team:
def __init__(self, members=None):
self.members = members or []
def add(self, member):
self.members.append(member)
def __len__(self):
return len(self.members)
def __str__(self):
return f"Team({', '.join(self.members)})"
team = Team()
team.add("Zhang San")
team.add("Li Si")
team.add("Wang Wu")
print(len(team)) # 3 — calls __len__
print(team) # Team(Zhang San, Li Si, Wang Wu)
(3) __getitem__: Making Objects Support Indexing
class Playlist:
def __init__(self):
self.songs = []
def add(self, song):
self.songs.append(song)
def __getitem__(self, index):
return self.songs[index]
def __len__(self):
return len(self.songs)
playlist = Playlist()
playlist.add("Sunny Day")
playlist.add("Seven Li Fragrance")
playlist.add("Nocturne")
print(playlist[0]) # Sunny Day — calls __getitem__
print(len(playlist)) # 3
# Supports slicing and iteration
for song in playlist:
print(song, end=" ") # Sunny Day Seven Li Fragrance Nocturne
3. Common Magic Methods Reference
class Demo:
def __init__(self, value): # Constructor
self.value = value
def __str__(self): # str() / print()
return str(self.value)
def __repr__(self): # repr() / debugging
return f"Demo({self.value})"
def __len__(self): # len()
return 1
def __bool__(self): # bool() / if condition
return self.value != 0
def __eq__(self, other): # ==
return self.value == other.value
def __lt__(self, other): # < (for sorting)
return self.value < other.value
def __add__(self, other): # + operator
return Demo(self.value + other.value)
def __getitem__(self, key): # obj[key]
return key
▶ Example: Fraction Class (Difficulty: Star-Star-Star)
▶ Try it YourselfOutput:
# Function defined successfully
4. @dataclass: Quickly Defining Data Classes
▶ Example: Shape Area and Perimeter (Difficulty ⭐⭐)
▶ Try it YourselfOutput:
Rectangle: area=12.00, perimeter=14.00
Circle: area=78.54, perimeter=31.42
Square: area=36.00, perimeter=24.00
Total area: 126.54
4. @dataclass: Quickly Defining Data Classes
Writing a class just for storing data, with manual __init__, __repr__, __eq__, is tedious. @dataclass generates them automatically:
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Student:
name: str
age: int
score: float = 0.0 # Default value
# Automatically gets __init__, __repr__, __eq__
p1 = Point(3.0, 4.0)
p2 = Point(3.0, 4.0)
print(p1) # Point(x=3.0, y=4.0)
print(p1 == p2) # True (compares all fields automatically)
s = Student("Zhang San", 20, 85.5)
print(s) # Student(name='Zhang San', age=20, score=85.5)
Tip: When to use
@dataclass: 1) A class mainly stores data with few methods. 2) You need boilerplate like__init__,__repr__,__eq__. 3) You don't want to write these manually. For simple cases, it's much more efficient than hand-writing classes.
5. Common Use Cases
- Duck typing: Python's
len(),for...in,sorted()are all protocol-based — implement the corresponding method and your class can use them. - Data containers: Implement
__getitem__+__len__to make your class support indexing and iteration. - Operator overloading:
__add__,__eq__,__lt__let custom classes support+,==,<etc. - Data classes:
@dataclassfor quickly defining configurations, DTOs, value objects.
❓ FAQ
print() calls __str__, len() calls __len__, == calls __eq__. You don't call them directly; just implement them, and Python calls them automatically at the right time.@dataclass and a regular class?@dataclass auto-generates the methods you're tired of writing. It's just a code generator — the generated class is identical to a hand-written one. Classes decorated with @dataclass can also have their own methods. If you find yourself repeatedly writing __init__ to store properties, it's time for @dataclass.📖 Summary
- Polymorphism: objects of different classes with the same-named method can be called the same way
- Duck typing: "If it quacks, it's a duck" — cares about methods, not types
- Magic methods:
__str__(printing),__len__(length),__getitem__(indexing),__eq__(equality),__add__(addition), etc. @dataclassauto-generates__init__,__repr__,__eq__— ideal for pure data classes
📝 Exercises
-
Beginner (Difficulty: Star): Define a
Bookclass implementing__str__and__len__(returning the page count). Create two books and outputprint(book)andlen(book). -
Intermediate (Difficulty: Star-Star): Use
@dataclassto define aProductclass (name, price, quantity). Implement__add__— merge two products with the same name (sum quantities, average price). Hint: Implement inside the @dataclass class. -
Advanced (Difficulty: Star-Star-Star): Define a
Deckclass (a deck of playing cards) implementing__len__,__getitem__(supporting indexing and slicing), andshuffle()(usingrandom.shuffle). Then write code to create a deck and deal the first 5 cards as a hand.