Ojasa Mirai

Ojasa Mirai

Python

Loading...

Learning Level

🟢 Beginner🔵 Advanced
Classes & ObjectsMethods & SelfInstance VariablesClass VariablesConstructors & InitializationInheritance BasicsPolymorphismEncapsulationMagic Methods & Dunder
Python/Oop/Magic Methods Dunder

✨ Magic Methods & Dunder — Special Object Behaviors

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.


🎯 What are Magic Methods?

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)

📝 String Representation Methods

`__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 item

➕ Operator Overloading

Magic 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)

🔍 Comparison Methods

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)

📊 Container Magic Methods

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}")

🔄 Type Conversion Methods

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 freezing

📚 Real-World Examples

Money/Currency Class

class 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: True

Matrix Class

class 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

✅ Key Takeaways

ConceptRemember
Magic MethodsSpecial methods with double underscores (__method__)
__str__User-friendly string representation
__repr__Technical representation for debugging
Operator OverloadingDefine 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
CustomizationMagic methods let objects behave like built-in types

🔗 What's Next?

You've completed the beginner OOP concepts! Explore advanced topics to deepen your understanding.

Explore Advanced OOP →


Ready to practice? Try challenges or take the quiz


Resources

Python Docs

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

Courses

PythonFastapiReactJSCloud

© 2026 Ojasa Mirai. All rights reserved.

TwitterGitHubLinkedIn