Python is a beautifully expressive and flexible programming language. One of its powerful features is the use of dunder methods — methods with double underscores before and after their names (hence “dunder” for “double underscore”). These methods allow you to define how your custom objects behave with built-in operations, such as arithmetic, comparisons, attribute access, and more.
In this article, we’ll dive deep into what Python dunder methods are, why they matter, and explore some of the most common ones with practical examples.
What Are Dunder Methods?
Dunder methods are special methods predefined by Python with names surrounded by double underscores, for example, __init__
, __str__
, and __add__
. They enable developers to customize the behavior of Python objects in certain scenarios, such as:
- Creating and initializing objects
- Representing objects as strings
- Comparing objects
- Supporting arithmetic operations
- Implementing container behavior (like indexing)
- Managing attribute access and more
Because Python internally calls these methods when corresponding operations are used on objects, they are often called magic methods or special methods.
Why Use Dunder Methods?
When you create a class in Python, your objects come with default behavior. But you might want your objects to behave more naturally or intuitively with Python’s syntax and operators. For example:
- Making two objects add together with
+
- Defining how your object is converted to a string for printing
- Enabling comparison using
==
or<
- Supporting indexing like a list or dictionary
- Allowing your object to be used in a
for
loop
Implementing the relevant dunder methods lets you achieve these behaviors. This results in cleaner, more Pythonic, and more maintainable code.
Common Python Dunder Methods and Examples
1. __init__(self, ...)
— Object Initialization
The constructor method called when you create a new instance of a class.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Alice", 30)
print(p.name) # Output is: Alice
2. __str__(self)
and __repr__(self)
— String Representations
__str__
: Defines the informal string representation, used by print()
and str()
.__repr__
: Defines the official string representation, ideally unambiguous and for developers.
class Person:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Person named {self.name}"
def __repr__(self):
return f"Person(name={self.name!r})"
p = Person("Bob")
print(str(p)) # Person named Bob
print(repr(p)) # Person(name='Bob')
3. __add__(self, other)
— Addition Operator
Defines behavior for the +
operator.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = 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})"
v1 = Vector(2, 3)
v2 = Vector(4, 1)
print(v1 + v2) # result: Vector(6, 4)
4. __len__(self)
— Length Support
class MyList:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
my_list = MyList([1, 2, 3, 4])
print(len(my_list)) # result is: 4
5. __getitem__(self, key)
— Indexing Support
Allows indexing and slicing.
class MyList:
def __init__(self, items):
self.items = items
def __getitem__(self, index):
return self.items[index]
my_list = MyList([10, 20, 30])
print(my_list[1]) # result is: 20
6. __eq__(self, other)
— Equality Operator
Defines behavior for the ==
operator.
class Person:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
p1 = Person("Alice")
p2 = Person("Alice")
p3 = Person("Bob")
print(p1 == p2) # True
print(p1 == p3) # False
7. __call__(self, ...)
— Make an Object Callable
Allows an instance to be called like a function.
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
print(f"Called {self.count} times")
c = Counter()
c() # Called 1 times
c() # Called 2 times
8. __enter__(self)
and __exit__(self, exc_type, exc_val, exc_tb)
— Context Managers
Enable use with with
statements.
class FileOpener:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'r')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
with FileOpener('test.txt') as f:
content = f.read()
Tips When Using Dunder Methods
Always follow the Python data model conventions to ensure expected behavior.
Implement both __str__
and __repr__
for clarity.
For operators like addition, also implement the reflected method (e.g., __radd__
) if needed.
Be careful with mutable objects and dunder methods that modify state.
Use dunder methods to make your classes behave like built-in types where appropriate — this improves usability and integration.
Conclusion
Python dunder methods are the “magic” that makes your custom classes feel like native Python objects. Mastering them allows you to write code that is elegant, intuitive, and idiomatic.
Whether you want to control how your objects print, compare, or even behave like functions or containers, dunder methods provide the hooks you need.
Start experimenting with dunder methods today and see how they can elevate your Python coding!
Leave a Reply