Magic methods (also called dunder methods) are special methods surrounded by double underscores that let you define how your class instances interact with Python’s built-in operations. When you use + on two objects, Python calls __add__. When you call len(), Python calls __len__. This reference covers the most commonly used magic methods organized by category.

Initialization and Lifecycle

MethodTriggered ByPurpose
__init__(self, ...)MyClass()Initialize instance attributes
__new__(cls, ...)Before __init__Control instance creation (rarely needed)
__del__(self)Object garbage collectedCleanup (prefer context managers instead)
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

user = User("Carl", "carl@example.com")

String Representation

MethodTriggered ByPurpose
__str__(self)str(obj), print(obj)Human-readable string
__repr__(self)repr(obj), REPL displayDeveloper/debug string
__format__(self, spec)format(obj, spec), f-stringsCustom formatting
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

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

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

p = Point(3, 4)
print(repr(p))  # Point(3, 4)
print(p)        # (3, 4)

Comparison Operators

MethodOperator
__eq__(self, other)==
__ne__(self, other)!=
__lt__(self, other)<
__le__(self, other)<=
__gt__(self, other)>
__ge__(self, other)>=
from functools import total_ordering

@total_ordering
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

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

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

freezing = Temperature(0)
boiling = Temperature(100)
print(freezing < boiling)   # True
print(freezing >= boiling)  # False (from @total_ordering)

With @total_ordering, you only need __eq__ and one of __lt__/__gt__ — Python derives the rest.

Arithmetic Operators

MethodOperatorReverse Method
__add__(self, other)+__radd__
__sub__(self, other)-__rsub__
__mul__(self, other)*__rmul__
__truediv__(self, other)/__rtruediv__
__floordiv__(self, other)//__rfloordiv__
__mod__(self, other)%__rmod__
__pow__(self, other)**__rpow__
class Money:
    def __init__(self, amount):
        self.amount = amount

    def __add__(self, other):
        return Money(self.amount + other.amount)

    def __mul__(self, factor):
        return Money(self.amount * factor)

    def __repr__(self):
        return f"${self.amount:.2f}"

price = Money(9.99)
tax = Money(0.80)
print(price + tax)    # $10.79
print(price * 3)      # $29.97

The __r*__ (reverse) methods handle cases where the left operand doesn’t support the operation: 3 * price calls price.__rmul__(3).

Bitwise Operators

MethodOperator
__lshift__(self, other)<<
__rshift__(self, other)>>
__and__(self, other)&
__or__(self, other)|
__xor__(self, other)^
__invert__(self)~

Container Methods

MethodTriggered ByPurpose
__len__(self)len(obj)Return length
__getitem__(self, key)obj[key]Get item by index/key
__setitem__(self, key, val)obj[key] = valSet item
__delitem__(self, key)del obj[key]Delete item
__contains__(self, item)item in objMembership test
__iter__(self)for x in objReturn iterator
class Config:
    def __init__(self, **kwargs):
        self._data = kwargs

    def __getitem__(self, key):
        return self._data[key]

    def __setitem__(self, key, value):
        self._data[key] = value

    def __contains__(self, key):
        return key in self._data

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

config = Config(debug=True, port=8080)
print(config["port"])         # 8080
print("debug" in config)     # True
print(len(config))            # 2

Context Manager Methods

MethodTriggered By
__enter__(self)Entering with block
__exit__(self, exc_type, exc_val, exc_tb)Leaving with block

See Context Managers in Python for detailed examples.

Callable Objects

MethodTriggered By
__call__(self, ...)obj()
class Validator:
    def __init__(self, min_val, max_val):
        self.min_val = min_val
        self.max_val = max_val

    def __call__(self, value):
        return self.min_val <= value <= self.max_val

is_valid_age = Validator(0, 150)
print(is_valid_age(25))    # True
print(is_valid_age(-1))    # False

Attribute Access

MethodTriggered By
__getattr__(self, name)obj.x (when x not found normally)
__setattr__(self, name, value)obj.x = value
__delattr__(self, name)del obj.x
__getattribute__(self, name)obj.x (always called)

These are powerful but easy to misuse — __getattribute__ in particular can cause infinite recursion if you’re not careful.


See also: