Python Magic Methods Quick Reference¶
Mental Model
Every row in these tables maps a piece of Python syntax to the dunder method Python calls behind the scenes. When you see a + b, think a.__add__(b). When you see len(x), think x.__len__(). This reference is the Rosetta Stone between Python's surface syntax and the protocol methods your classes can implement.
How to Use This Page
This is a quick reference, not a tutorial. Use it to recall method names, signatures, and protocols when you already understand the concepts. To learn how each method works, see the dedicated pages: Introduction, Arithmetic, Comparison, String Representation, Object Lifecycle.
Most Commonly Used Magic Methods¶
Initialization & Representation¶
| Method | Purpose | Example |
|---|---|---|
__init__(self, ...) |
Constructor | obj = MyClass(args) |
__repr__(self) |
Developer-friendly string | repr(obj) |
__str__(self) |
User-friendly string | str(obj), print(obj) |
Comparison Operators¶
| Method | Operator | Example |
|---|---|---|
__eq__(self, other) |
== |
a == b |
__ne__(self, other) |
!= |
a != b |
__lt__(self, other) |
< |
a < b |
__le__(self, other) |
<= |
a <= b |
__gt__(self, other) |
> |
a > b |
__ge__(self, other) |
>= |
a >= b |
Arithmetic Operators¶
| Method | Operator | Example |
|---|---|---|
__add__(self, other) |
+ |
a + b |
__sub__(self, other) |
- |
a - b |
__mul__(self, other) |
* |
a * b |
__truediv__(self, other) |
/ |
a / b |
__floordiv__(self, other) |
// |
a // b |
__mod__(self, other) |
% |
a % b |
__pow__(self, other) |
** |
a ** b |
Reverse Arithmetic (for other + self)¶
| Method | Operator | Example |
|---|---|---|
__radd__(self, other) |
+ |
5 + obj |
__rsub__(self, other) |
- |
5 - obj |
__rmul__(self, other) |
* |
5 * obj |
In-place Operations¶
| Method | Operator | Example |
|---|---|---|
__iadd__(self, other) |
+= |
a += b |
__isub__(self, other) |
-= |
a -= b |
__imul__(self, other) |
*= |
a *= b |
Unary Operators¶
| Method | Operator | Example |
|---|---|---|
__neg__(self) |
- |
-obj |
__pos__(self) |
+ |
+obj |
__abs__(self) |
abs() |
abs(obj) |
__invert__(self) |
~ |
~obj |
Container Methods¶
| Method | Purpose | Example |
|---|---|---|
__len__(self) |
Length | len(obj) |
__getitem__(self, key) |
Get item | obj[key] |
__setitem__(self, key, val) |
Set item | obj[key] = val |
__delitem__(self, key) |
Delete item | del obj[key] |
__contains__(self, item) |
Membership | item in obj |
__iter__(self) |
Iteration | for x in obj |
__next__(self) |
Next item | next(obj) |
__reversed__(self) |
Reverse | reversed(obj) |
Type Conversion¶
| Method | Purpose | Example |
|---|---|---|
__int__(self) |
Convert to int | int(obj) |
__float__(self) |
Convert to float | float(obj) |
__bool__(self) |
Convert to bool | bool(obj), if obj: |
__str__(self) |
Convert to string | str(obj) |
__bytes__(self) |
Convert to bytes | bytes(obj) |
Special Methods¶
| Method | Purpose | Example |
|---|---|---|
__call__(self, ...) |
Make callable | obj(args) |
__hash__(self) |
Hash value | hash(obj), set([obj]) |
__format__(self, spec) |
String format | f"{obj:spec}" |
Context Managers¶
| Method | Purpose | Example |
|---|---|---|
__enter__(self) |
Enter context | with obj: |
__exit__(self, ...) |
Exit context | (automatic) |
Attribute Access¶
| Method | Purpose | When Called |
|---|---|---|
__getattr__(self, name) |
Get missing attr | obj.name (if not found) |
__setattr__(self, name, val) |
Set any attr | obj.name = val |
__delattr__(self, name) |
Delete attr | del obj.name |
__getattribute__(self, name) |
Get any attr | obj.name (always) |
Quick Tips¶
When implementing __eq__, also implement __hash__¶
```python def eq(self, other): return self.value == other.value
def hash(self): return hash(self.value) ```
Return NotImplemented for unsupported types¶
python
def __add__(self, other):
if not isinstance(other, MyClass):
return NotImplemented
return MyClass(self.value + other.value)
Use object.__setattr__() to avoid recursion¶
python
def __setattr__(self, name, value):
# Validate here
object.__setattr__(self, name, value)
Context manager pattern¶
```python def enter(self): # Setup code return self # or resource
def exit(self, exc_type, exc_val, exc_tb): # Cleanup code return False # False = propagate exceptions ```
Protocol Checklist¶
Before implementing dunder methods, use this table to identify which methods your class needs:
| Goal | Required Methods |
|---|---|
| Numeric type | __add__, __mul__, __eq__, __lt__, __repr__, __hash__ |
| Sequence (read-only) | __len__, __getitem__ |
| Mutable sequence | __len__, __getitem__, __setitem__, __delitem__ |
| Hashable object | __eq__ + __hash__ (must be immutable) |
| Sortable object | __eq__ + __lt__ (use @total_ordering for the rest) |
| Context manager | __enter__, __exit__ |
| Callable object | __call__ |
| Iterable | __iter__ (or __getitem__ as fallback) |
Design Rules¶
Only implement dunder methods when they match user expectations:
- Does the operation make semantic sense?
Vector + Vectoris natural;User + Useris not. - Is it consistent? If
a + bworks,b + ashould too (when appropriate). Implement__radd__. - What about other types? Always return
NotImplementedfor unsupported types --- never raiseTypeErrordirectly in the method. - Mutable or immutable? Decide upfront. If immutable, implement
__hash__. If mutable, set__hash__ = None. - Does
__eq__exist? Then__hash__must too (or be explicitlyNone).
When NOT to Implement Dunder Methods
Dunder methods should make code more intuitive, not clever. Avoid implementing them when:
- The operation's meaning is unclear --- if
User + Userrequires explanation, use a named method instead. - It reduces readability --- operators should be immediately obvious to someone reading the code.
- A normal method would be clearer ---
account.merge(other)is better thanaccount + otherif "addition" does not describe the operation.
The test: would a new team member understand a + b without reading the class definition? If not, use a regular method.
Common Patterns¶
Immutable Class (tuple-like)¶
Implement: __init__, __repr__, __str__, __eq__, __hash__, __getitem__, __len__
Container Class (list-like)¶
Implement: __init__, __len__, __getitem__, __setitem__, __delitem__, __contains__, __iter__
Numeric Class¶
Implement: __init__, __repr__, __add__, __sub__, __mul__, __truediv__, __neg__, __abs__, __eq__, __lt__
Callable Class (function-like)¶
Implement: __init__, __call__, __repr__
Context Manager Class¶
Implement: __init__, __enter__, __exit__
Remember¶
- Magic methods should be intuitive - follow the principle of least surprise
- Always document your magic methods
- Test edge cases thoroughly
- Use
super()when inheriting - Consider performance implications
Exercises¶
Exercise 1.
Using this reference, create a TimeSlot class that implements at least 6 different magic methods from different categories (representation, comparison, arithmetic, container, context manager, truth). List which magic method corresponds to which category.
Solution to Exercise 1
from functools import total_ordering
@total_ordering
class TimeSlot:
"""Uses 6+ magic methods from different categories."""
# Lifecycle
def __init__(self, start_hour, end_hour):
self.start = start_hour
self.end = end_hour
# Representation
def __repr__(self):
return f"TimeSlot({self.start}, {self.end})"
def __str__(self):
return f"{self.start}:00-{self.end}:00"
# Comparison
def __eq__(self, other):
return (self.start, self.end) == (other.start, other.end)
def __lt__(self, other):
return self.start < other.start
# Arithmetic
def __add__(self, hours):
return TimeSlot(self.start, self.end + hours)
# Truth
def __bool__(self):
return self.end > self.start
# Container (length = duration)
def __len__(self):
return max(0, self.end - self.start)
ts = TimeSlot(9, 12)
print(repr(ts)) # Representation: TimeSlot(9, 12)
print(str(ts)) # Representation: 9:00-12:00
print(ts == TimeSlot(9, 12)) # Comparison: True
print(ts + 2) # Arithmetic: TimeSlot(9, 14)
print(bool(ts)) # Truth: True
print(len(ts)) # Container: 3
Exercise 2.
Write a Config class that uses __getitem__ and __setitem__ (container methods), __contains__ (membership), __len__ (sizing), __repr__ (representation), and __bool__ (truth). It should behave like a dictionary. Create and demonstrate all six methods.
Solution to Exercise 2
class Config:
def __init__(self, **kwargs):
self._data = dict(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)
def __repr__(self):
return f"Config({self._data})"
def __bool__(self):
return len(self._data) > 0
cfg = Config(host="localhost", port=8080)
print(cfg["host"]) # localhost
cfg["debug"] = True
print("debug" in cfg) # True
print(len(cfg)) # 3
print(repr(cfg)) # Config({...})
print(bool(cfg)) # True
print(bool(Config())) # False
Exercise 3.
Build a FileWrapper class that uses __init__ (lifecycle), __enter__/__exit__ (context manager), __len__ (returns file size), __str__ (returns filename), and __bool__ (returns whether file has content). Simulate the file operations without actually opening files.
Solution to Exercise 3
class FileWrapper:
def __init__(self, filename, content=""):
self.filename = filename
self.content = content
self.is_open = False
def __enter__(self):
self.is_open = True
print(f"Opened {self.filename}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.is_open = False
print(f"Closed {self.filename}")
return False
def __len__(self):
return len(self.content)
def __str__(self):
return self.filename
def __bool__(self):
return len(self.content) > 0
with FileWrapper("data.txt", "Hello World") as f:
print(str(f)) # data.txt
print(len(f)) # 11
print(bool(f)) # True
print(bool(FileWrapper("empty.txt"))) # False