Advanced Dunder Methods
Make your classes work seamlessly with Python's built-in operations.
Dunder methods (double underscore methods) allow you to customize how your objects interact with Python’s built-in operations. While we covered basics in the introduction, here we explore advanced usage patterns.
Operator Overloading
Section titled “Operator Overloading”Make your objects work with Python operators:
class Vector: """2D vector with operator overloading""" def __init__(self, x: float, y: float): self.x = x self.y = y
def __add__(self, other): """Vector addition: v1 + v2""" if isinstance(other, Vector): return Vector(self.x + other.x, self.y + other.y) return NotImplemented
def __sub__(self, other): """Vector subtraction: v1 - v2""" if isinstance(other, Vector): return Vector(self.x - other.x, self.y - other.y) return NotImplemented
def __mul__(self, scalar): """Scalar multiplication: v * 5""" if isinstance(scalar, (int, float)): return Vector(self.x * scalar, self.y * scalar) return NotImplemented
def __rmul__(self, scalar): """Right multiplication: 5 * v""" return self.__mul__(scalar)
def __eq__(self, other): """Equality: v1 == v2""" if isinstance(other, Vector): return self.x == other.x and self.y == other.y return False
def __repr__(self): return f"Vector({self.x}, {self.y})"
v1 = Vector(3, 4)v2 = Vector(1, 2)print(v1 + v2) # Vector(4, 6)print(v1 - v2) # Vector(2, 2)print(v1 * 2) # Vector(6, 8)print(2 * v1) # Vector(6, 8) - uses __rmul__print(v1 == v2) # FalseMaking Objects Callable
Section titled “Making Objects Callable”Use __call__ to make instances callable like functions:
class Multiplier: """Callable object that multiplies by a factor""" def __init__(self, factor: float): self.factor = factor
def __call__(self, value: float) -> float: """Make object callable""" return value * self.factor
double = Multiplier(2.0)triple = Multiplier(3.0)
print(double(5)) # 10.0print(triple(5)) # 15.0
# Can be used in higher-order functionsnumbers = [1, 2, 3, 4, 5]doubled = list(map(double, numbers))print(doubled) # [2.0, 4.0, 6.0, 8.0, 10.0]Index Access with __getitem__ and __setitem__
Section titled “Index Access with __getitem__ and __setitem__”Make your objects work like lists or dictionaries:
class ShoppingCart: """Shopping cart that supports index access""" def __init__(self): self.items = []
def add_item(self, item: str): self.items.append(item)
def __getitem__(self, index): """Enable: cart[0]""" return self.items[index]
def __setitem__(self, index, value): """Enable: cart[0] = 'new_item'""" self.items[index] = value
def __len__(self): """Enable: len(cart)""" return len(self.items)
def __delitem__(self, index): """Enable: del cart[0]""" del self.items[index]
cart = ShoppingCart()cart.add_item("Laptop")cart.add_item("Mouse")
print(cart[0]) # "Laptop" - uses __getitem__cart[1] = "Keyboard" # Uses __setitem__print(cart[1]) # "Keyboard"print(len(cart)) # 2 - uses __len__del cart[0] # Uses __delitem__print(len(cart)) # 1Comparison Operators
Section titled “Comparison Operators”Implement rich comparison methods:
class Money: """Money class with comparison operators""" def __init__(self, amount: float, currency: str = "USD"): self.amount = amount self.currency = currency
def __eq__(self, other): """== operator""" if isinstance(other, Money): return self.amount == other.amount and self.currency == other.currency return False
def __lt__(self, other): """< operator""" if isinstance(other, Money) and self.currency == other.currency: return self.amount < other.amount return NotImplemented
def __le__(self, other): """<= operator""" return self < other or self == other
def __gt__(self, other): """> operator""" if isinstance(other, Money) and self.currency == other.currency: return self.amount > other.amount return NotImplemented
def __ge__(self, other): """>= operator""" return self > other or self == other
def __repr__(self): return f"Money({self.amount}, '{self.currency}')"
m1 = Money(100.0)m2 = Money(50.0)m3 = Money(100.0)
print(m1 > m2) # Trueprint(m1 < m2) # Falseprint(m1 >= m3) # Trueprint(m1 <= m2) # FalseString Representations
Section titled “String Representations”Control how your objects are displayed:
class Product: """Product with custom string representations""" def __init__(self, name: str, price: float, sku: str): self.name = name self.price = price self.sku = sku
def __str__(self): """Informal string - for end users""" return f"{self.name} - ${self.price:.2f}"
def __repr__(self): """Official string - for developers/debugging""" return f"Product(name='{self.name}', price={self.price}, sku='{self.sku}')"
def __format__(self, format_spec): """Custom formatting with f-strings""" if format_spec == "short": return self.name elif format_spec == "price": return f"${self.price:.2f}" return str(self)
product = Product("Laptop", 999.99, "LAP-001")print(str(product)) # "Laptop - $999.99"print(repr(product)) # "Product(name='Laptop', price=999.99, sku='LAP-001')"print(f"{product:short}") # "Laptop"print(f"{product:price}") # "$999.99"Complete Dunder Methods Reference
Section titled “Complete Dunder Methods Reference”| Category | Method | Purpose | Example |
|---|---|---|---|
| Initialization | __init__ | Object creation | obj = MyClass() |
| String | __str__ | User-friendly string | str(obj) |
__repr__ | Developer string | repr(obj) | |
__format__ | Custom formatting | f"{obj:format}" | |
| Arithmetic | __add__ | Addition | obj1 + obj2 |
__sub__ | Subtraction | obj1 - obj2 | |
__mul__ | Multiplication | obj1 * obj2 | |
__truediv__ | Division | obj1 / obj2 | |
__mod__ | Modulo | obj1 % obj2 | |
| Comparison | __eq__ | Equality | obj1 == obj2 |
__lt__ | Less than | obj1 < obj2 | |
__le__ | Less or equal | obj1 <= obj2 | |
__gt__ | Greater than | obj1 > obj2 | |
__ge__ | Greater or equal | obj1 >= obj2 | |
| Container | __len__ | Length | len(obj) |
__getitem__ | Index access | obj[key] | |
__setitem__ | Index assignment | obj[key] = val | |
__delitem__ | Index deletion | del obj[key] | |
__contains__ | Membership | item in obj | |
| Iteration | __iter__ | Iterator | for item in obj |
__next__ | Next item | next(iterator) | |
| Callable | __call__ | Make callable | obj() |
| Context | __enter__ | Context entry | with obj: |
__exit__ | Context exit | with obj: | |
| Attributes | __getattr__ | Missing attribute | obj.missing |
__setattr__ | Attribute set | obj.attr = val | |
__delattr__ | Attribute delete | del obj.attr |