Difficulty: Advanced
What is the Python data model? How do dunder methods enable Pythonic class design?
The Python data model is the set of interfaces (dunder methods) that classes can implement to integrate with the language and built-in functions.
By implementing dunder methods, your class: - Works with len(), iter(), in, +, -, *, [], ==, <, etc. - Prints informatively with repr()/str() - Integrates with context managers (with) - Works with async for, await
Key dunder groups: - Representation: __repr__, __str__, __format__ - Comparison: __eq__, __lt__, __le__, __gt__, __ge__, __hash__ - Arithmetic: __add__, __radd__, __iadd__, __mul__, etc. - Container: __len__, __getitem__, __setitem__, __contains__, __iter__ - Context Manager: __enter__, __exit__ - Callable: __call__
from collections.abc import MutableSequence
class FixedList:
"""A list with a maximum size."""
def __init__(self, max_size, iterable=()):
self._max = max_size
self._data = list(iterable)
if len(self._data) > max_size:
raise ValueError(f'Too many items (max {max_size})')
def __repr__(self):
return f'FixedList({self._max}, {self._data!r})'
def __str__(self):
return str(self._data)
def __len__(self):
return len(self._data)
def __getitem__(self, index):
return self._data[index]
def __setitem__(self, index, value):
self._data[index] = value
def __contains__(self, item):
return item in self._data
def __iter__(self):
return iter(self._data)
def append(self, item):
if len(self._data) >= self._max:
raise OverflowError(f'List is full (max {self._max})')
self._data.append(item)
fl = FixedList(3, [1, 2])
fl.append(3)
print(repr(fl)) # FixedList(3, [1, 2, 3])
print(len(fl)) # 3
print(2 in fl) # True
print([x * 2 for x in fl]) # [2, 4, 6]
__len__, __getitem__, __iter__, __contains__ make the class a proper sequence. for loops, list comprehensions, and in all work because of these dunder methods.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f'Vector({self.x}, {self.y})'
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __radd__(self, other): # other + self
if other == 0:
return self
return self.__add__(other)
def __mul__(self, scalar): # vec * 3
return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar): # 3 * vec
return self.__mul__(scalar)
def __abs__(self):
return (self.x2 + self.y2) 0.5
def __bool__(self):
return bool(self.x or self.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(3, 6)
print(3 * v1) # Vector(3, 6)
print(abs(v2)) # 5.0
print(bool(Vector(0, 0))) # False
print(sum([v1, v2], Vector(0,0))) # Vector(4, 6)
__radd__ enables sum() which starts with 0 + v1. __mul__ and __rmul__ enable both vec*3 and 3*vec. __bool__ controls truthiness.
Data Model, Dunder Methods, Protocol, Operator Overloading, Custom Containers