Python Magic Methods (Dunder Methods)
“Magic methods” (also known as dunder methods, short for “double underscore”) allow you to define how your custom objects behave with built-in Python operations like operators (+, -, <), standard functions (len(), str()), and iteration loops.
1. Magic Methods Cheat Sheet
Initialization & Construction
These methods control how an object is created and set up.
| Method | Description | Example Trigger |
|---|---|---|
__init__(self, ...) |
The constructor. Initializes a new instance. | obj = MyClass() |
__new__(cls, ...) |
Creates the instance before __init__. Rare, used for Singletons/Metaclasses. |
obj = MyClass() |
__del__(self) |
The destructor. Called when an object is garbage collected. | del obj |
Example:
1
2
3
4
5
6
class Book:
def __init__(self, title, pages):
self.title = title
self.pages = pages
b = Book("Python 101", 200)
String Representation
These control how your object looks when printed or inspected.
| Method | Description | Example Trigger |
|---|---|---|
__str__(self) |
User-friendly string representation. | print(obj) or str(obj) |
__repr__(self) |
Developer-friendly string (unambiguous, helpful for debugging). | obj in console or repr(obj) |
1
2
3
4
5
6
7
8
9
10
11
12
13
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return f"Point at ({self.x}, {self.y})"
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
p = Point(1, 2)
print(p) # Output: Point at (1, 2)
print([p]) # Output: [Point(x=1, y=2)] (Lists use __repr__)
Comparison Operators
Allow objects to be compared (<, >, ==).
In Python, the __lt__ (less than) method is the “keystone” comparison operator.
The built-in functions sorted(), list.sort(), min(), and max() rely almost exclusively on the < operator to order items. As long as Python knows how to determine if “Item A is smaller than Item B,” it can figure out the rest of the order.
Pro Tip: If you want all comparison operators (<=, >=, ==) to work by only writing one or two methods, use the @total_ordering decorator from the functools module. It fills in the missing ones for you automatically.
| Method | Operator | Description |
|---|---|---|
__eq__(self, other) |
== |
Equality |
__lt__(self, other) |
< |
Less than (Enables sorting!) |
__le__(self, other) |
<= |
Less than or equal |
__gt__(self, other) |
> |
Greater than |
__ge__(self, other) |
>= |
Greater than or equal |
__ne__(self, other) |
!= |
Not equal |
1
2
3
4
5
6
7
8
9
10
11
class Score:
def __init__(self, value):
self.value = value
def __lt__(self, other):
# Enables sorting logic
return self.value < other.value
s1 = Score(10)
s2 = Score(20)
print(s1 < s2) # True
Arithmetic Operators
| Method | Operator | Description |
|---|---|---|
__add__(self, other) |
+ |
Addition |
__sub__(self, other) |
- |
Subtraction |
__mul__(self, other) |
* |
Multiplication |
__truediv__(self, other) |
/ |
Division |
__floordiv__(self, other) |
// |
Floor Division |
__mod__(self, other) |
% |
Modulo |
__pow__(self, other) |
** |
Power |
1
2
3
4
5
6
7
8
9
10
class Wallet:
def __init__(self, money):
self.money = money
def __add__(self, other):
return Wallet(self.money + other.money)
w1 = Wallet(50)
w2 = Wallet(30)
w3 = w1 + w2 # w3.money is 80
Container & Sequence Methods
| Method | Description | Example Trigger |
|---|---|---|
__len__(self) |
Returns the length. | len(obj) |
__getitem__(self, key) |
Access an item by index/key. | obj[key] |
__setitem__(self, key, value) |
Set an item by index/key. | obj[key] = value |
__delitem__(self, key) |
Delete an item by index/key. | del obj[key] |
__contains__(self, item) |
Check membership. | item in obj |
__iter__(self) |
Returns an iterator object. | for x in obj: |
2. Understanding post_init
__post_init__ is not a standard built-in Python magic method for regular classes. It is specific to Dataclasses (@dataclass decorator, introduced in Python 3.7).
When you use the @dataclass decorator, Python automatically generates the __init__ method for you. __post_init__is a “hook” that runs immediately after that auto-generated code finishes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from dataclasses import dataclass
@dataclass
class Rectangle:
width: float
height: float
area: float = 0.0 # We will calculate this automatically
def __post_init__(self):
# This runs immediately AFTER the auto-generated __init__
if self.width < 0 or self.height < 0:
raise ValueError("Dimensions cannot be negative")
# Calculate derived attribute
self.area = self.width * self.height
# Usage
r = Rectangle(5, 10)
print(r.area) # Output: 50.0