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.


Related Built-in Functions:sorted() print() len()

1. What is Polymorphism

Polymorphism means "many forms" — the same interface, different implementations:

PYTHON
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:

TEXT 📖 Display only
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 Yourself

Output:

TEXT 📖 Display only
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

PYTHON
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()

PYTHON
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

PYTHON
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

PYTHON
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 Yourself

Output:

TEXT 📖 Display only
# Function defined successfully


4. @dataclass: Quickly Defining Data Classes

▶ Example: Shape Area and Perimeter (Difficulty ⭐⭐)

▶ Try it Yourself

Output:

TEXT 📖 Display only
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:

PYTHON
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


Related Built-in Functions:sorted() print() len()

❓ FAQ

Q What's the difference between magic methods and regular methods?
A Magic methods are called by Python's built-in operations — 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.
Q What's the difference between @dataclass and a regular class?
A @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.
Q Python is dynamically typed — does polymorphism come naturally?
A Yes. Python's "duck typing" makes polymorphism possible without inheritance — just having methods with the same name is enough. Other languages (like Java/C++) need interfaces or abstract classes for polymorphism. The benefit is flexibility; the downside is that type-checking can't catch errors as easily.

📖 Summary


📝 Exercises

  1. Beginner (Difficulty: Star): Define a Book class implementing __str__ and __len__ (returning the page count). Create two books and output print(book) and len(book).

  2. Intermediate (Difficulty: Star-Star): Use @dataclass to define a Product class (name, price, quantity). Implement __add__ — merge two products with the same name (sum quantities, average price). Hint: Implement inside the @dataclass class.

  3. Advanced (Difficulty: Star-Star-Star): Define a Deck class (a deck of playing cards) implementing __len__, __getitem__ (supporting indexing and slicing), and shuffle() (using random.shuffle). Then write code to create a deck and deal the first 5 cards as a hand.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏