How to Define Special Methods (Dunder Methods) in Python Classes
Special methods, often called dunder methods (double underscore), are the mechanism Python uses to implement operator overloading and built-in function behavior for custom objects. By defining these methods, you can make your classes behave like standard types (e.g., using + to add objects, len() to get length, or print() to show a readable string).
This guide explains how to define the most essential special methods to create Pythonic classes.
Understanding Dunder Methods
Special methods are predefined methods starting and ending with double underscores (e.g., __init__). You do not call them directly (e.g., obj.__len__()). Instead, Python calls them when you use corresponding built-in functions (e.g., len(obj)).
Initialization (__init__)
The most common special method is __init__, which initializes a new instance of a class.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
p1 = Product("Laptop", 1000)
print(p1.name)
Output:
Laptop
String Representation (__str__ vs __repr__)
These methods control how your object looks when printed.
__str__: Should be readable for end-users (used byprint()).__repr__: Should be unambiguous for developers (used in the interactive shell or debugger).
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __str__(self):
# Readable format
return f"{self.name} costs ${self.price}"
def __repr__(self):
# Developer format (ideally valid python code to recreate the object)
return f"Product(name='{self.name}', price={self.price})"
p = Product("Mouse", 25)
print(str(p)) # Calls __str__
print(repr(p)) # Calls __repr__
Output:
Mouse costs $25
Product(name='Mouse', price=25)
Operator Overloading (__add__, __eq__)
You can define how operators like +, -, ==, or < work with your objects.
Overloading Addition (+)
To allow obj1 + obj2, define __add__.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
# Return a new Vector with summed coordinates
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(1, 4)
v3 = v1 + v2
print(v3)
Output:
Vector(3, 7)
Overloading Equality (==)
To allow custom comparison logic, define __eq__.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if not isinstance(other, Vector):
return False
return self.x == other.x and self.y == other.y
v1 = Vector(2, 3)
v2 = Vector(2, 3)
print(f"Are vectors equal? {v1 == v2}")
Output:
Are vectors equal? True
Container Emulation (__len__, __getitem__)
To make your class behave like a list or dictionary (indexing, iteration, length), implement container methods.
class Team:
def __init__(self, members):
self.members = members
def __len__(self):
# Called by len(team)
return len(self.members)
def __getitem__(self, index):
# Called by team[index]
return self.members[index]
team = Team(["Alice", "Bob", "Charlie"])
print(f"Team Size: {len(team)}")
print(f"First Member: {team[0]}")
# Since __getitem__ is defined, iteration works automatically!
for member in team:
print(f"- {member}")
Output:
Team Size: 3
First Member: Alice
- Alice
- Bob
- Charlie
Conclusion
Defining special methods makes your classes integrate seamlessly with Python's syntax.
- Always define
__repr__for debugging. - Define
__str__if you need a pretty output for users. - Implement
__eq__if you want to compare object values rather than memory identities. - Use arithmetic methods (
__add__, etc.) only when mathematical operations make logical sense for your object.