| name | python-protocols |
| description | Fluent Python - data model, protocols, dunder methods, Pythonic design |
Luciano Ramalho - Fluent Python
Apply Luciano Ramalho's expertise from "Fluent Python." Master Python's data model, protocols, and the patterns that make code truly Pythonic.
Core Philosophy
The Data Model Is Everything
Python's special methods (dunder methods) define how objects behave. Understanding them is the key to Pythonic code.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'Vector({self.x!r}, {self.y!r})'
def __abs__(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __bool__(self):
return bool(abs(self))
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
v = Vector(3, 4)
abs(v)
bool(v)
v + v
v * 3
Protocols Over Inheritance
Python uses duck typing. Implement protocols (sets of methods) rather than inheriting from base classes.
class Deck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit)
for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
deck = Deck()
len(deck)
deck[0]
deck[-1]
deck[12::13]
Card('Q', 'hearts') in deck
for card in deck:
(card)
Prescriptive Rules
Implement repr for All Classes
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'{self.__class__.__name__}({self.x!r}, {self.y!r})'
Use @property for Computed Attributes
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError('Radius must be positive')
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
@property
def diameter(self):
return self._radius * 2
Use slots When Memory Matters
class Pixel:
__slots__ = ('x', 'y', 'color')
def __init__(self, x, y, color):
self.x = x
self.y = y
self.color = color
Callable Objects
class Averager:
def __init__(self):
self.series = []
def __call__(self, new_value):
self.series.append(new_value)
return sum(self.series) / len(self.series)
avg = Averager()
avg(10)
avg(11)
avg(12)
Context Manager Protocol
class DatabaseConnection:
def __init__(self, connection_string):
self.connection_string = connection_string
self.connection = None
def __enter__(self):
self.connection = connect(self.connection_string)
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
self.connection.close()
return False
with DatabaseConnection('...') as conn:
conn.execute('SELECT ...')
Key Protocols
| Protocol | Methods | Enables |
|---|
| Container | __contains__ | in operator |
| Sized | __len__ | len() |
| Iterable | __iter__ | for loops |
| Sequence | __getitem__, __len__ | Indexing, slicing, iteration |
| Mapping | __getitem__, __contains__, __iter__ | Dict-like access |
| Callable | __call__ | obj() syntax |
| Context Manager | __enter__, __exit__ | with statement |
| Hashable | __hash__, __eq__ | Dict keys, set members |
Anti-Patterns
| Pattern | Ramalho Fix |
|---|
Bare class with no __repr__ | Always implement __repr__ |
| Getter/setter methods | Use @property |
isinstance() checks everywhere | Duck typing, protocols |
Inheriting from list/dict | Compose with delegation |
| Mutable default arguments | Use None and create in __init__ |
Review Checklist
Key Insight
"Pythonic code is not about tricks or idioms—it's about leveraging Python's data model. When you implement the right special methods, your objects integrate naturally with the language."