Dunder methods are how Python's built-in operators and functions integrate with custom classes: defining __str__ customizes what str()/print() show, __eq__ customizes ==, __len__ customizes len(), __add__ customizes the + operator, and so on for dozens of others. This system, often called 'operator overloading' or adherence to Python's 'data model', is what makes it possible for a custom class to feel like a natural part of the language, supporting iteration, indexing, comparison, or arithmetic, rather than requiring special-cased method calls.
1Understanding Dunder Methods
Dunder methods are how Python's built-in operators and functions integrate with custom classes: defining __str__ customizes what str()/print() show, __eq__ customizes ==, __len__ customizes len(), __add__ customizes the + operator, and so on for dozens of others. This system, often called 'operator overloading' or adherence to Python's 'data model', is what makes it possible for a custom class to feel like a natural part of the language, supporting iteration, indexing, comparison, or arithmetic, rather than requiring special-cased method calls.
Define __repr__ on every class you write, even if you also define __str__ — __repr__ is the fallback used by the REPL, debuggers, and inside containers like lists, and a good one, ideally showing how to recreate the object, makes debugging significantly easier.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
print(Vector(1, 2) + Vector(3, 4))2Practical Example
Here is a real-world application of Dunder Methods showing how it is used in production Python code.
class Playlist:
def __init__(self, songs):
self.songs = songs
def __len__(self):
return len(self.songs)
def __getitem__(self, index):
return self.songs[index]
p = Playlist(["Song A", "Song B", "Song C"])
print(len(p))
print(p[1])3Best Practices
Follow these guidelines when working with Dunder Methods:
1. Implement __repr__ on custom classes for better debugging output in the REPL, logs, and inside containers
2. Implement __eq__, and __hash__ alongside it if instances need to be hashable, when instances should be compared by value instead of identity
3. Only implement the dunder methods your class's usage actually calls for — implementing __add__ on a class where addition doesn't make sense adds confusing, unused surface area
Tip: Define __repr__ on every class you write, even if you also define __str__ — __repr__ is the fallback used by the REPL, debuggers, and inside containers like lists, and a good one, ideally showing how to recreate the object, makes debugging significantly easier.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
print(Vector(1, 2) + Vector(3, 4))