#1 Data Analytics Program in India
₹2,499₹1,499Enroll Now
7 min read
•Question 20 of 41medium

Magic Methods (Dunder)

Special methods in Python classes.

What You'll Learn

  • What magic/dunder methods are
  • String representation: str vs repr
  • Operator overloading for custom classes
  • Container and iteration protocols
  • Callable objects and context managers

Understanding Magic Methods

Magic methods (also called dunder methods for "double underscore") are special methods that Python calls automatically in response to certain operations. They let you customize how your objects behave with built-in functions and operators.

String Representation

code.pyPython
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        """Human-readable string (for print, str())"""
        return f"{self.name}, {self.age} years old"

    def __repr__(self):
        """Developer-readable string (for debugging, repr())"""
        return f"Person('{self.name}', {self.age})"

p = Person("Alice", 30)
print(p)        # Alice, 30 years old (__str__)
print(repr(p))  # Person('Alice', 30) (__repr__)
print([p])      # [Person('Alice', 30)] (uses __repr__ in containers)

Comparison Operators

code.pyPython
from functools import total_ordering

@total_ordering  # Generates other comparisons from __eq__ and __lt__
class Money:
    def __init__(self, amount):
        self.amount = amount

    def __eq__(self, other):
        return self.amount == other.amount

    def __lt__(self, other):
        return self.amount < other.amount

    def __hash__(self):
        return hash(self.amount)

m1 = Money(100)
m2 = Money(200)
print(m1 < m2)   # True
print(m1 <= m2)  # True (from @total_ordering)
print(m1 == m2)  # False

Arithmetic Operators

code.pyPython
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

    def __rmul__(self, scalar):
        """Right multiplication: scalar * vector"""
        return self.__mul__(scalar)

    def __neg__(self):
        return Vector(-self.x, -self.y)

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(2, 3)
v2 = Vector(1, 1)
print(v1 + v2)   # Vector(3, 4)
print(v1 - v2)   # Vector(1, 2)
print(v1 * 2)    # Vector(4, 6)
print(3 * v1)    # Vector(6, 9) (uses __rmul__)
print(-v1)       # Vector(-2, -3)

Container Protocol

code.pyPython
class Deck:
    def __init__(self):
        self.cards = ['A', '2', '3', '4', '5']

    def __len__(self):
        return len(self.cards)

    def __getitem__(self, index):
        return self.cards[index]

    def __setitem__(self, index, value):
        self.cards[index] = value

    def __delitem__(self, index):
        del self.cards[index]

    def __contains__(self, item):
        return item in self.cards

deck = Deck()
print(len(deck))      # 5
print(deck[0])        # 'A'
print('A' in deck)    # True
deck[0] = 'K'         # Uses __setitem__

Callable Objects

code.pyPython
class Multiplier:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, x):
        return x * self.factor

double = Multiplier(2)
triple = Multiplier(3)

print(double(5))   # 10
print(triple(5))   # 15
print(callable(double))  # True

Context Managers

code.pyPython
class Timer:
    def __enter__(self):
        import time
        self.start = time.time()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        self.elapsed = time.time() - self.start
        print(f"Elapsed: {self.elapsed:.4f}s")
        return False  # Don't suppress exceptions

with Timer():
    sum(range(1000000))
# Elapsed: 0.0234s

Common Magic Methods Reference

MethodTriggered By
__init__Object creation
__str__str(), print()
__repr__repr(), debugger
__eq__, __lt__==, <
__add__, __sub__+, -
__mul__, __truediv__*, /
__len__len()
__getitem__obj[key]
__setitem__obj[key] = value
__contains__in operator
__iter__for loop, iter()
__call__obj()
__enter__, __exit__with statement
__hash__hash(), dict keys

Interview Tip

When asked about magic methods:

  1. str for humans, repr for developers
  2. Use @total_ordering with eq and lt
  3. rmul for right-side operations (2 * obj)
  4. call makes instances callable
  5. enter/exit for context managers
  6. Implement hash if overriding eq