
Python
Magic methods (dunder methods or dunders) are special methods with double underscores that customize how objects behave. They let you define what happens when you use operators, convert objects to strings, compare them, or use built-in functions with your objects.
Magic methods are special methods that Python calls automatically in specific situations. They start and end with double underscores (dunder = "double underscore"). By defining these methods, you customize how your objects work with Python's built-in operations.
class Number:
def __init__(self, value):
self.value = value
def __str__(self):
# Called by str() and print()
return f"Number: {self.value}"
def __repr__(self):
# Called by repr() or in interpreter
return f"Number({self.value})"
num = Number(42)
print(num) # Calls __str__()
# Output: Number: 42
print(repr(num)) # Calls __repr__()
# Output: Number(42)`__str__()` returns a user-friendly string. `__repr__()` returns a technical representation. These control how your objects appear when printed or inspected.
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
def __str__(self):
return f"'{self.title}' by {self.author} ({self.pages} pages)"
def __repr__(self):
return f"Book('{self.title}', '{self.author}', {self.pages})"
book = Book("1984", "George Orwell", 328)
print(book) # Uses __str__()
# Output: '1984' by George Orwell (328 pages)
print(repr(book)) # Uses __repr__()
# Output: Book('1984', 'George Orwell', 328)
# Useful in debugging
books = [Book("1984", "Orwell", 328), Book("Brave New World", "Huxley", 311)]
print(books) # Shows repr of each itemMagic methods let you define how operators work with your objects. You can make + do addition, - do subtraction, == do comparison, and more.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
# Define what + does
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
# Define what - does
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
# Define what * does
return Vector(self.x * scalar, self.y * scalar)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(1, 4)
v3 = v1 + v2 # Calls __add__
print(v3) # Output: Vector(3, 7)
v4 = v1 - v2 # Calls __sub__
print(v4) # Output: Vector(1, -1)
v5 = v1 * 3 # Calls __mul__
print(v5) # Output: Vector(6, 9)Define how to compare objects using ==, <, >, <=, >=, !=. This allows objects to be compared meaningfully.
class Student:
def __init__(self, name, gpa):
self.name = name
self.gpa = gpa
def __eq__(self, other):
# Define == (equality)
return self.gpa == other.gpa
def __lt__(self, other):
# Define < (less than)
return self.gpa < other.gpa
def __le__(self, other):
# Define <= (less than or equal)
return self.gpa <= other.gpa
def __gt__(self, other):
# Define > (greater than)
return self.gpa > other.gpa
def __str__(self):
return f"{self.name} (GPA: {self.gpa})"
s1 = Student("Alice", 3.8)
s2 = Student("Bob", 3.5)
s3 = Student("Charlie", 3.8)
print(s1 == s3) # True - same GPA (calls __eq__)
print(s1 > s2) # True - Alice has higher GPA (calls __gt__)
print(s2 < s1) # True - Bob has lower GPA (calls __lt__)
# Sort students by GPA
students = [s1, s2, s3]
students.sort() # Uses __lt__
for s in students:
print(s)Define how your objects work with Python's container operations like len(), indexing, and membership testing.
class Playlist:
def __init__(self, name):
self.name = name
self.songs = []
def __len__(self):
# Called by len()
return len(self.songs)
def __getitem__(self, index):
# Called by obj[index]
return self.songs[index]
def __setitem__(self, index, song):
# Called by obj[index] = value
self.songs[index] = song
def __contains__(self, song):
# Called by song in obj
return song in self.songs
def __iter__(self):
# Called by for loop
return iter(self.songs)
def add_song(self, song):
self.songs.append(song)
def __str__(self):
return f"{self.name}: {', '.join(self.songs)}"
playlist = Playlist("Rock Classics")
playlist.add_song("Bohemian Rhapsody")
playlist.add_song("Stairway to Heaven")
playlist.add_song("Hotel California")
print(len(playlist)) # Output: 3 (__len__)
print(playlist[0]) # Output: Bohemian Rhapsody (__getitem__)
"Stairway to Heaven" in playlist # True (__contains__)
for song in playlist: # Uses __iter__
print(f" - {song}")Define how objects behave when converted to other types using int(), float(), str(), bool().
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
def __str__(self):
return f"{self.celsius}°C"
def __int__(self):
# Called by int()
return int(self.celsius)
def __float__(self):
# Called by float()
return float(self.celsius)
def __bool__(self):
# Called by bool() - True if not freezing
return self.celsius > 0
temp1 = Temperature(20)
print(int(temp1)) # Output: 20 (__int__)
print(float(temp1)) # Output: 20.0 (__float__)
print(bool(temp1)) # Output: True (__bool__)
temp2 = Temperature(-5)
print(bool(temp2)) # Output: False - below freezingclass Money:
def __init__(self, amount, currency="USD"):
self.amount = amount
self.currency = currency
def __add__(self, other):
if self.currency != other.currency:
raise ValueError("Cannot add different currencies")
return Money(self.amount + other.amount, self.currency)
def __sub__(self, other):
if self.currency != other.currency:
raise ValueError("Cannot subtract different currencies")
return Money(self.amount - other.amount, self.currency)
def __mul__(self, scalar):
return Money(self.amount * scalar, self.currency)
def __eq__(self, other):
return self.amount == other.amount and self.currency == other.currency
def __lt__(self, other):
if self.currency != other.currency:
raise ValueError("Cannot compare different currencies")
return self.amount < other.amount
def __str__(self):
return f"${self.amount:.2f} {self.currency}"
m1 = Money(100, "USD")
m2 = Money(50, "USD")
m3 = Money(150, "USD")
print(m1 + m2) # Output: $150.00 USD
print(m1 - m2) # Output: $50.00 USD
print(m1 * 2) # Output: $200.00 USD
print(m1 == m3) # Output: False
print(m2 < m1) # Output: Trueclass Matrix:
def __init__(self, rows, cols, data=None):
self.rows = rows
self.cols = cols
if data is None:
self.data = [[0] * cols for _ in range(rows)]
else:
self.data = data
def __str__(self):
return "\n".join([str(row) for row in self.data])
def __getitem__(self, key):
row, col = key
return self.data[row][col]
def __setitem__(self, key, value):
row, col = key
self.data[row][col] = value
def __eq__(self, other):
if self.rows != other.rows or self.cols != other.cols:
return False
return self.data == other.data
def __len__(self):
return self.rows * self.cols
m = Matrix(2, 3)
m[0, 0] = 1
m[0, 1] = 2
m[1, 0] = 3
print(m) # Display matrix
print(m[0, 1]) # Output: 2
print(len(m)) # Output: 6| Concept | Remember |
|---|---|
| Magic Methods | Special methods with double underscores (__method__) |
| __str__ | User-friendly string representation |
| __repr__ | Technical representation for debugging |
| Operator Overloading | Define what +, -, *, ==, <, etc. do |
| __len__ | Define behavior for len() |
| __getitem__ | Define behavior for obj[index] |
| __contains__ | Define behavior for in operator |
| __iter__ | Make object iterable in for loops |
| Customization | Magic methods let objects behave like built-in types |
You've completed the beginner OOP concepts! Explore advanced topics to deepen your understanding.
Ready to practice? Try challenges or take the quiz
Resources
Ojasa Mirai
Master AI-powered development skills through structured learning, real projects, and verified credentials. Whether you're upskilling your team or launching your career, we deliver the skills companies actually need.
Learn Deep • Build Real • Verify Skills • Launch Forward