- rtshkmr's digital garden/
- Readings/
- Books/
- Fluent Python: Clear, Concise, and Effective Programming – Luciano Ramalho/
- Chapter 1. The Python Data Model/
Chapter 1. The Python Data Model
Table of Contents
Seeing python as a “framework” #
This gives us some use cases / purpose for implementing special methods to interface w python as a “framework”
the special methods are dunder methods
We implement special methods when we want our objects to support and interact with fundamental language constructs such as: • Collections • Attribute access • Iteration (including asynchronous iteration using async for) • Operator overloading • Function and method invocation • String representation and formatting • Asynchronous programming using await • Object creation and destruction • Managed contexts using the with or async with statements
What’s New in This Chapter #
A Pythonic Card Deck #
this is a demonstrative example on how we can adapt to the “interface” for the “framework” that is python.
Class Composition and how Delegation pattern in the data model helps #
our getitem delegates to the [] operator of self._cards, our deck automatically supports slicing. Here’s
- The use of base classes allows OOP benefits for us such as being able to delegate functionality.
- Delegation is different from forwarding
- this python example is closer to the concept of “forwarding” actually
Yes, the concept of "delegation" referenced in *Fluent Python* when discussing how `__getitem__` delegates to the `[]` operator of `self._cards` is directly related to a general programming language principle—specifically, the **delegation pattern** in object-oriented programming.
## Delegation in Programming
**Delegation** is a software design pattern where an object (the *delegator*) passes responsibility for a particular task to another object (the *delegate*) that is better suited to handle it. The delegator maintains the context and control, but leverages the delegate's specialized capabilities to fulfill certain operations[1][2][3].
In the context of Python's data model, when you write:
```python
def __getitem__(self, position):
return self._cards[position]
```
You are *delegating* the item access operation to the underlying list (`self._cards`), which already knows how to handle slicing and indexing. This is a classic example of the delegation pattern: your class does not implement slicing logic itself, but instead relies on a delegate (the list) to do so.
## Delegation vs. Forwarding
- **Delegation**: The delegator object maintains the context and can pass itself (or relevant context) to the delegate. In some languages, this means that `self` (or `this`) inside the delegate's methods refers back to the original object, not the delegate[3][2].
- **Forwarding**: The delegator simply passes the request to the delegate, and the delegate operates in its own context. The delegate does not know about the original sender[3][2].
In Python, the example above is technically closer to *forwarding* because the list (`self._cards`) does not receive any special context from the delegator. However, in the broader sense, this is often referred to as delegation in Python literature.
## Delegation Pattern in Object-Oriented Programming
The delegation pattern allows composition to achieve the same code reuse as inheritance. Instead of inheriting behavior from a parent class, you compose your class with instances of other classes (the delegates) and forward certain operations to them[2][1]. This makes your code more modular and flexible.
## Language Support
Some languages have built-in support for delegation (e.g., Kotlin's `by` keyword), but in Python, delegation is typically implemented manually by calling methods on composed objects[2].
## Summary Table
| Concept | Description | Example in Python |
|--------------|-----------------------------------------------------------------------------|----------------------------------|
| Delegation | Object passes responsibility to another, often with context preservation | `self._cards[position]` (loosely)|
| Forwarding | Object passes request to another, delegate operates in its own context | `self._cards[position]` (strict) |
## Key Takeaway
Delegation is a general programming pattern for code reuse and modularity, where one object relies on another to perform certain tasks. In Python, this is often seen when a class delegates an operation to an internal object, as in the `__getitem__` example from *Fluent Python*[1][2][3].
[1] https://dev.to/kalashin1/delegation-4h29
[2] https://handwiki.org/wiki/Delegation_pattern
[3] https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming)
[4] https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/
[5] https://en.wikipedia.org/wiki/Delegation_(computing)
[6] https://www.cs.unc.edu/~dewan/comp401/current/Class%20Notes/22_Delegation_Notes.pdf
[7] https://developer.apple.com/library/archive/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html
[8] https://langdev.stackexchange.com/questions/3586/are-delegates-still-relevant-in-modern-c
[9] https://www.reddit.com/r/Kotlin/comments/1928bzd/can_someone_explain_me_the_concept_of_delegations/
[10] https://perso.limsi.fr/pointal/python:delegator
How Special Methods Are Used #
- NOTE: built-ins that are variable sized under the hood have an
ob_sizeattribute that holds the size of that collection. This makes it faster to calllen(my_object)since it’s not really a function call, the interpreter just reads off the pointer.
1) Emulating Numeric Types #
- it’s all about implementing the number-class related dunder methods, then anything can behave like a number
2) String Representation __repr__ #
- repr is different from string in the sense that it’s supposed to be a visual representation of the creation of that object. Therefore, it should be unambiguous, and if possible, match source code necessary to recreate the represented object
- repr is not really for display purposes, that’s what
strbuiltin is for - implement the special function
reprfirst thenstr
3) Boolean Value of a Custom Type #
default: forward to bool() elif len() #
By default, instances of user-defined classes are considered truthy, unless either bool or len is implemented. Basically, bool(x) calls x._bool_() and uses the result. If bool is not implemented, Python tries to invoke x._len_(), and if that returns zero, bool returns False. Otherwise bool returns True.
4) Collection API #
The collections api is new, and it unifies the 3 following interfaces:
• Iterable to support for, unpacking, and other forms of iteration
• Sized to support the len built-in function
• Container to support the in operator
There’s no need to inherit from these ABCs specifically, as long as the dunder methods are implemented then it’s considered as satisfying the ABC
Specialisations of Collection #
Three very important specializations of Collection are: • Sequence, formalizing the interface of built-ins like list and str • Mapping, implemented by dict, collections.defaultdict, etc. • Set, the interface of the set and frozenset built-in types
I want to use the vocabulary here when describing what primitives I want to use.
python dicts are “ordered” in the sense that the insertion order is preserved #
- there’s nothing else we can do about the ordering property (e.g. manipulating[rearranging] the order and such)
Overview of Special Methods #
- there’s a bunch, the latest ones are more on the async support side, they will be covered throughout the book
Why len Is Not a Method #
“Practicality beats purity”.
- there’s no method call for
len(x)when x is a CPython built-in because it’s a direct read of a C-struct - for custom objects, we can implement the dunder method
__len__ - it kinda looks like a functional style (since len is a fn) in a OOP-styled language. To reconcile this, we can think of
absandlenas unary functions!
Chapter Summary #
Further Reading #
Python’s DataModel can be seen as a MetaObject Protocol #
Metaobjects The Art of the Metaobject Protocol (AMOP) is my favorite computer book title. But I mention it because the term metaobject protocol is useful to think about the Python Data Model and similar features in other languages. The metaobject part refers to the objects that are the building blocks of the language itself. In this context, protocol is a synonym of interface. So a metaobject protocol is a fancy synonym for object model: an API for core language constructs.