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#
| Method | Triggered By | Purpose |
|---|
__init__(self, ...) | MyClass() | Initialize instance attributes |
__new__(cls, ...) | Before __init__ | Control instance creation (rarely needed) |
__del__(self) | Object garbage collected | Cleanup (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#
| Method | Triggered By | Purpose |
|---|
__str__(self) | str(obj), print(obj) | Human-readable string |
__repr__(self) | repr(obj), REPL display | Developer/debug string |
__format__(self, spec) | format(obj, spec), f-strings | Custom 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#
| Method | Operator |
|---|
__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#
| Method | Operator | Reverse 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#
| Method | Operator |
|---|
__lshift__(self, other) | << |
__rshift__(self, other) | >> |
__and__(self, other) | & |
__or__(self, other) | | |
__xor__(self, other) | ^ |
__invert__(self) | ~ |
Container Methods#
| Method | Triggered By | Purpose |
|---|
__len__(self) | len(obj) | Return length |
__getitem__(self, key) | obj[key] | Get item by index/key |
__setitem__(self, key, val) | obj[key] = val | Set item |
__delitem__(self, key) | del obj[key] | Delete item |
__contains__(self, item) | item in obj | Membership test |
__iter__(self) | for x in obj | Return 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#
| Method | Triggered 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#
| Method | Triggered 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#
| Method | Triggered 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: