Skip to content

Commit b3ca920

Browse files
committed
Data Model
1 parent cf3161c commit b3ca920

File tree

4 files changed

+416
-13
lines changed

4 files changed

+416
-13
lines changed

01-data-model/README.md

Lines changed: 311 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,323 @@
1-
# The Python Data Model
1+
Perfeito! Aqui está um `README.md` com um resumo técnico e detalhado de tudo o que você viu até agora no seu estudo sobre o **Python Data Model**, com foco no funcionamento interno do **CPython**, `PyObject`, `PyTypeObject` e os **slots C** como `sq_length`.
22

3-
Sample code for Chapter 1 of _Fluent Python 2e_ by Luciano Ramalho (O'Reilly, 2020)
3+
---
44

5-
## Running the tests
5+
````markdown
6+
# Estudo sobre o Python Data Model (com base no Fluent Python)
67

7-
### Doctests
8+
## 📖 Contexto
9+
Este documento resume os principais conceitos estudados até agora sobre o **modelo de dados do Python** e sua implementação no **CPython**, com base no capítulo 1 do livro *Fluent Python*, de Luciano Ramalho.
810

9-
Use Python's standard ``doctest`` module to check stand-alone doctest file:
11+
---
1012

11-
$ python3 -m doctest frenchdeck.doctest -v
13+
## 🧩 O que é o Python Data Model?
1214

13-
And to check doctests embedded in a module:
15+
O **data model** é o conjunto de regras que define como os objetos em Python devem se comportar em diferentes contextos — incluindo operadores (`+`, `==`, `in`), funções built-in (`len()`, `abs()`, `iter()`), e estruturas de controle (`for`, `with`, `if`).
1416

15-
$ python3 -m doctest vector2d.py -v
17+
O modelo é expresso através dos **special methods** (também chamados de *dunder methods*), como:
1618

17-
### Jupyter Notebook
19+
- `__len__`
20+
- `__getitem__`
21+
- `__add__`
22+
- `__call__`
23+
- `__enter__`, `__exit__`
24+
- `__iter__`, `__next__`
1825

19-
Install ``pytest`` and the ``nbval`` plugin:
26+
---
2027

21-
$ pip install pytest nbval
28+
## 💡 Exemplo: `FrenchDeck`
2229

23-
Run:
30+
```python
31+
import collections
2432

25-
$ pytest --nbval
33+
Card = collections.namedtuple('Card', ['rank', 'suit'])
34+
35+
class FrenchDeck:
36+
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
37+
suits = 'spades diamonds clubs hearts'.split()
38+
39+
def __init__(self):
40+
self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]
41+
42+
def __len__(self):
43+
return len(self._cards)
44+
45+
def __getitem__(self, position):
46+
return self._cards[position]
47+
````
48+
49+
Esse objeto se comporta como uma **sequence** sem herdar de `list`. Ele implementa os métodos `__len__` e `__getitem__`, e por isso:
50+
51+
* Pode ser iterado: `for card in deck: ...`
52+
* Pode ser fatiado: `deck[12::13]`
53+
* Funciona com `random.choice(deck)`
54+
* Aceita `len(deck)`
55+
* Funciona com `in`, `sorted`, etc.
56+
57+
---
58+
59+
## ⚙️ O que acontece quando fazemos `len(deck)`?
60+
61+
Python chama a função built-in len(), que é implementada em C (no código-fonte do CPython).
62+
63+
### Se `deck` for um objeto built-in:
64+
65+
* `len(deck)` chama a função C `PySequence_Size(obj)`
66+
* Ela acessa `obj->ob_type->tp_as_sequence->sq_length`
67+
* Esse campo (`sq_length`) aponta para uma função C como `list_length()`
68+
* Resultado: chamada direta em C, altíssima performance
69+
70+
### Se `deck` for um objeto definido em Python (como `FrenchDeck`):
71+
72+
* Durante a criação da classe, o CPython detecta que ela tem `__len__`
73+
* Cria um *wrapper C* chamado `slot_sq_length`
74+
* Esse wrapper é armazenado em `tp_as_sequence->sq_length`
75+
* Ao chamar `len(deck)`, o CPython executa:
76+
77+
* `slot_sq_length(deck)`
78+
* Que chama `deck.__len__()` por nome, uma única vez, e cacheia o acesso
79+
80+
---
81+
82+
📍 Caminho interno percorrido pelo CPython:
83+
84+
Etapa 1: Python chama função built-in
85+
──────────────────────────────────────
86+
len(deck)
87+
└─► builtin_len(obj) ← função C em builtins.c
88+
└─► PyObject_Length(obj) ← wrapper
89+
└─► PySequence_Size(obj)
90+
91+
Etapa 2: Acessa struct de tipo do objeto
92+
──────────────────────────────────────
93+
deck → PyObject
94+
95+
└──► ob_type → PyTypeObject (FrenchDeck)
96+
97+
└──► tp_as_sequence → PySequenceMethods
98+
99+
Etapa 3: Verifica e chama o slot de comprimento
100+
──────────────────────────────────────
101+
PySequence_Size(obj) faz:
102+
103+
if (tp_as_sequence && tp_as_sequence->sq_length) {
104+
return tp_as_sequence->sq_length(obj);
105+
}
106+
107+
Ou seja:
108+
109+
→ lê ponteiro sq_length
110+
→ que aponta para slot_sq_length()
111+
→ chama slot_sq_length(obj)
112+
113+
Etapa 4: slot_sq_length (função C wrapper)
114+
──────────────────────────────────────
115+
slot_sq_length(obj) faz:
116+
117+
- Acessa atributo "__len__" na instância (só na 1ª vez)
118+
- Chama obj.__len__() em Python
119+
- Recebe PyObject (número)
120+
- Converte para Py_ssize_t
121+
- Retorna o valor
122+
123+
Etapa 5: Execução do método Python
124+
──────────────────────────────────────
125+
def __len__(self):
126+
return len(self._cards)
127+
128+
Etapa 6: len(self._cards)
129+
──────────────────────────────────────
130+
Como self._cards é uma list, o caminho é:
131+
132+
len(self._cards)
133+
└─► PySequence_Size(list_obj)
134+
└─► list_obj->ob_type->tp_as_sequence->sq_length
135+
└─► list_length(list_obj) ← função C nativa
136+
└─► return Py_SIZE(list_obj)
137+
138+
139+
## 🔬 Estruturas internas no CPython
140+
141+
### 🧱 `PyObject`
142+
143+
Estrutura mínima de qualquer objeto:
144+
145+
```c
146+
typedef struct {
147+
Py_ssize_t ob_refcnt; // contador de referências
148+
PyTypeObject *ob_type; // ponteiro para o tipo
149+
} PyObject;
150+
```
151+
152+
---
153+
154+
### Slots C no CPython
155+
156+
157+
**Slots** (como `sq_length`, `nb_add`, `mp_subscript`, etc.) são **campos dentro de structs em C** (como `PySequenceMethods`, `PyNumberMethods`, `PyMappingMethods`) que:
158+
159+
* **Armazenam ponteiros para funções C**.
160+
* Essas funções **implementam o comportamento** associado a cada parte do **Python Data Model**.
161+
162+
---
163+
164+
📌 Em outras palavras:
165+
166+
> **Slots são ponteiros que apontam para funções C que implementam as operações definidas pelo data model.**
167+
168+
Por exemplo:
169+
170+
| Slot | Implementa | Quando é usado? |
171+
| -------------- | ------------- | -------------------------- |
172+
| `sq_length` | `len(obj)` | Quando se chama `len(obj)` |
173+
| `sq_item` | `obj[i]` | Indexação |
174+
| `nb_add` | `obj1 + obj2` | Soma |
175+
| `mp_subscript` | `obj[key]` | Acesso via chave |
176+
| `tp_call` | `obj()` | Objeto chamável |
177+
| `tp_str` | `str(obj)` | Conversão para string |
178+
179+
---
180+
181+
🎯 Exemplo: `sq_length`
182+
183+
```c
184+
typedef Py_ssize_t (*lenfunc)(PyObject *);
185+
186+
typedef struct {
187+
lenfunc sq_length; // ← slot
188+
// outros slots omitidos
189+
} PySequenceMethods;
190+
```
191+
192+
Esse slot (`sq_length`) **aponta para** uma função como:
193+
194+
* `slot_sq_length` (wrapper para `__len__`)
195+
* ou `list_length` (implementação nativa para `list`)
196+
197+
198+
### 🧠 `PyTypeObject`
199+
200+
Representa o “tipo” (classe) de um objeto.
201+
202+
Contém:
203+
204+
* `tp_name`: nome do tipo
205+
* `tp_basicsize`: tamanho da instância
206+
* Ponteiros para funções C como `tp_init`, `tp_new`, `tp_dealloc`
207+
* Ponteiros para **sub-estruturas** como:
208+
209+
* `tp_as_sequence` → struct `PySequenceMethods`
210+
* `tp_as_number` → struct `PyNumberMethods`
211+
* `tp_as_mapping` → struct `PyMappingMethods`
212+
213+
---
214+
215+
### 🧷 `PySequenceMethods` (sub-struct)
216+
217+
```c
218+
typedef struct {
219+
lenfunc sq_length; // → usado por len()
220+
binaryfunc sq_concat; // → usado por +
221+
ssizeargfunc sq_repeat; // → usado por *
222+
ssizeargfunc sq_item; // → usado por []
223+
// ...
224+
} PySequenceMethods;
225+
```
226+
227+
No caso de `FrenchDeck`, `sq_length` aponta para `slot_sq_length`.
228+
229+
---
230+
231+
## 🧠 Conceitos importantes
232+
233+
| Conceito | Explicação |
234+
| ----------------- | ------------------------------------------------------------- |
235+
| **CPython** | Implementação oficial da linguagem Python, escrita em C. |
236+
| **Compilador** | Programa que traduz código para outro formato (ex: bytecode). |
237+
| **Interpretador** | Executa o bytecode linha por linha (CPython faz os dois). |
238+
| **PyObject** | Estrutura base para toda instância em Python. |
239+
| **PyTypeObject** | Estrutura que representa a "classe" de um objeto em C. |
240+
| **Slot C** | Campo da struct do tipo que guarda ponteiros para funções C. |
241+
| **`sq_length`** | Slot usado para `len(obj)`. |
242+
| **Bytecode** | Instruções intermediárias interpretadas pelo CPython. |
243+
244+
---
245+
246+
## ✅ Conclusões
247+
248+
* `len(obj)` em tipos embutidos (`list`, `str`) chama diretamente funções C via slot `sq_length`.
249+
* `obj.__len__()` executa bytecode e envolve lookup, sendo mais lento e menos idiomático.
250+
* Ao definir `__len__` numa classe Python, o CPython preenche automaticamente o slot C com um wrapper.
251+
* **Slots C são fundamentais** para performance e integração entre objetos e a linguagem.
252+
253+
---
254+
255+
## 📚 Fontes
256+
257+
* [Fluent Python – Capítulo 1](https://www.oreilly.com/library/view/fluent-python/9781491946237/)
258+
* [CPython source code – `typeobject.c`](https://github.com/python/cpython/blob/main/Objects/typeobject.c)
259+
* [Documentação oficial: C API `PyTypeObject`](https://docs.python.org/3/c-api/typeobj.html)
260+
261+
```
262+
263+
---
264+
265+
Etapa 1: Instância do objeto
266+
────────────────────────────────────────────
267+
+-------------------+
268+
deck ──►| PyObject | ← instancia de FrenchDeck
269+
| ob_refcnt | ← contador de referências
270+
| ob_type ─────────┼─────► PyTypeObject (FrenchDeck)
271+
+-------------------+
272+
273+
Etapa 2: Descrição do tipo (classe FrenchDeck)
274+
───────────────────────────────────────────────
275+
276+
# tp_as_sequence é um ponteiro para uma struct do tipo PySequenceMethods
277+
# Se o tipo implementa o sequence protocol (como __len__, __getitem__, etc), esse campo aponta para uma instância da struct PySequenceMethods.
278+
279+
PyTypeObject (FrenchDeck)
280+
+-----------------------------+
281+
| tp_name = "FrenchDeck" |
282+
| tp_as_sequence ─────────┐ |
283+
+--------------------------│--+
284+
285+
Etapa 3: Tabela de slots da sequência
286+
────────────────────────────────────────────
287+
PySequenceMethods
288+
+-----------------------------+
289+
| sq_length ─────────────┐ |
290+
| sq_item | |
291+
| sq_concat | |
292+
+------------------------│----+
293+
294+
Etapa 4: Ponteiro para função C genérica
295+
────────────────────────────────────────────
296+
slot_sq_length ← função C gerada pelo CPython
297+
298+
// Implementação:
299+
slot_sq_length(PyObject *self) {
300+
// chama self.__len__() em Python
301+
return _PyObject_CallMethodId(self, &PyId___len__, NULL);
302+
}
303+
304+
Etapa 5: Método Python definido pelo usuário
305+
────────────────────────────────────────────
306+
def __len__(self):
307+
return len(self._cards)
308+
309+
self._cards = list → objeto embutido Python
310+
311+
Etapa 6: Caminho interno até list_length
312+
────────────────────────────────────────────
313+
self._cards ─► PyObject (tipo: list)
314+
315+
└─► PyTypeObject (list)
316+
317+
└─► tp_as_sequence → PySequenceMethods
318+
319+
└─► sq_length = list_length
320+
321+
list_length(PyObject *list) {
322+
return Py_SIZE(list);
323+
}

01-data-model/data-model.ipynb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@
9292
"len(deck)"
9393
]
9494
},
95+
{
96+
"cell_type": "code",
97+
"execution_count": null,
98+
"metadata": {},
99+
"outputs": [],
100+
"source": []
101+
},
102+
{
103+
"cell_type": "code",
104+
"execution_count": null,
105+
"metadata": {},
106+
"outputs": [],
107+
"source": []
108+
},
95109
{
96110
"cell_type": "code",
97111
"execution_count": 4,
@@ -265,6 +279,7 @@
265279
}
266280
],
267281
"source": [
282+
"\n",
268283
"for card in deck:\n",
269284
" print(card)"
270285
]

0 commit comments

Comments
 (0)