This chapter delves into the special methods in Python classes, known as Dunder Methods (methods surrounded by double underscores). These methods allow you to overload built-in operators, control object representation, and enable objects to work with constructs like the with statement.
Dunder Methods (from Double Underscores) are powerful, built-in methods that allow you to customize how your custom objects interact with Python’s built-in syntax, functions, and operators. They are never called directly; instead, they are invoked automatically by the interpreter when a specific operation occurs (e.g., when you use the + operator, Python calls the __add__ method).
1. Object Representation: __str__ and __repr__
These two methods control how your object is displayed.
| Dunder Method | Purpose | Invoked By | Audience |
__str__ | Provides a user-friendly, readable string representation. | print(obj) or str(obj) | End-users |
__repr__ | Provides an unambiguous, official string representation, ideally allowing the object to be recreated. | Python console (REPL) or repr(obj) | Developers |
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# For developers: unambiguous representation
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
# For end-users: readable representation
def __str__(self):
return f"({self.x}, {self.y})"
p = Point(5, 10)
print(p) # Calls __str__ | Output: (5, 10)
p # Calls __repr__ in the console | Output: Point(x=5, y=10)
2. Operator Overloading (Arithmetic and Comparison)
By defining specific dunder methods, you can customize how standard Python operators behave when applied to your class instances. This is a form of Polymorphism (P2.5).
A. Arithmetic Operators
| Operator | Dunder Method | Purpose |
+ | __add__(self, other) | Defines behavior for addition. |
- | __sub__(self, other) | Defines behavior for subtraction. |
* | __mul__(self, other) | Defines behavior for multiplication. |
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# Overloads the + operator
def __add__(self, other):
# Returns a NEW Vector object
new_x = self.x + other.x
new_y = self.y + other.y
return Vector(new_x, new_y)
v1 = Vector(2, 3)
v2 = Vector(5, 1)
v3 = v1 + v2 # Internally calls v1.__add__(v2)
# v3 is a Vector object with coordinates (7, 4)
B. Comparison Operators
| Operator | Dunder Method | Purpose |
== | __eq__(self, other) | Defines behavior for equality (==). |
> | __gt__(self, other) | Defines behavior for greater than (>). |
3. Context Management Protocol
Dunder methods are essential for enabling an object to be used with the safe with statement (Context Manager, P2.2). An object must implement both __enter__ and __exit__.
| Dunder Method | Purpose | Invoked When |
__enter__(self) | Setup phase. Returns the resource (e.g., file object) to be used inside the with block. | The with statement is entered. |
__exit__(self, exc_type, exc_val, exc_tb) | Cleanup phase. Guarantees resource release, even if an exception occurs. | The with statement is exited. |
class Timer:
def __enter__(self):
self.start_time = time.time()
print("Timer started.")
return self # Return the Timer instance
def __exit__(self, exc_type, exc_val, exc_tb):
end_time = time.time()
elapsed = end_time - self.start_time
print(f"Timer stopped. Elapsed time: {elapsed:.2f}s")
# Resource cleanup/logging logic goes here
import time
with Timer() as t:
time.sleep(1)
# The Timer starts and stops automatically, guaranteed by __enter__ and __exit__.
