Agent Skill
2/7/2026

python-protocols

Fluent Python - data model, protocols, dunder methods, Pythonic design

O
objective
0GitHub Stars
1Views
npx skills add Objective-Arts/lens

SKILL.md

Namepython-protocols
DescriptionFluent Python - data model, protocols, dunder methods, Pythonic design

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)

# Now Vector works naturally with Python
v = Vector(3, 4)
abs(v)        # 5.0
bool(v)       # True
v + v         # Vector(6, 8)
v * 3         # Vector(9, 12)

Protocols Over Inheritance

Python uses duck typing. Implement protocols (sets of methods) rather than inheriting from base classes.

# The Sequence protocol: __len__ and __getitem__
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]

# Now Deck supports: iteration, slicing, in, reversed, sorted
deck = Deck()
len(deck)           # 52
deck[0]             # Card(rank='2', suit='spades')
deck[-1]            # Card(rank='A', suit='hearts')
deck[12::13]        # Four aces
Card('Q', 'hearts') in deck  # True
for card in deck:   # Iteration works
    print(card)

Prescriptive Rules

Implement repr for All Classes

# BAD: Default repr is useless
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

# >>> Point(3, 4)
# <Point object at 0x...>

# GOOD: Informative repr
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})'

# >>> Point(3, 4)
# Point(3, 4)

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

# Millions of Pixels? __slots__ saves significant memory

Callable Objects

# Classes can be callable
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)  # 10.0
avg(11)  # 10.5
avg(12)  # 11.0

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  # Don't suppress exceptions

with DatabaseConnection('...') as conn:
    conn.execute('SELECT ...')

Key Protocols

ProtocolMethodsEnables
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

PatternRamalho Fix
Bare class with no __repr__Always implement __repr__
Getter/setter methodsUse @property
isinstance() checks everywhereDuck typing, protocols
Inheriting from list/dictCompose with delegation
Mutable default argumentsUse None and create in __init__

Review Checklist

  • Does every class have a useful __repr__?
  • Are computed attributes using @property?
  • Are we implementing protocols instead of inheriting?
  • Is __hash__ consistent with __eq__?
  • Are context managers used for resource management?
  • Is __slots__ used for memory-critical classes?

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."

Skills Info
Original Name:python-protocolsAuthor:objective