diff --git a/01-data-model/README.md b/01-data-model/README.md index 18e2865..9482d6a 100644 --- a/01-data-model/README.md +++ b/01-data-model/README.md @@ -1,25 +1,323 @@ -# The Python Data Model +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`. -Sample code for Chapter 1 of _Fluent Python 2e_ by Luciano Ramalho (O'Reilly, 2020) +--- -## Running the tests +````markdown +# Estudo sobre o Python Data Model (com base no Fluent Python) -### Doctests +## 📖 Contexto +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. -Use Python's standard ``doctest`` module to check stand-alone doctest file: +--- - $ python3 -m doctest frenchdeck.doctest -v +## 🧩 O que é o Python Data Model? -And to check doctests embedded in a module: +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`). - $ python3 -m doctest vector2d.py -v +O modelo é expresso através dos **special methods** (também chamados de *dunder methods*), como: -### Jupyter Notebook +- `__len__` +- `__getitem__` +- `__add__` +- `__call__` +- `__enter__`, `__exit__` +- `__iter__`, `__next__` -Install ``pytest`` and the ``nbval`` plugin: +--- - $ pip install pytest nbval +## 💡 Exemplo: `FrenchDeck` -Run: +```python +import collections - $ pytest --nbval +Card = collections.namedtuple('Card', ['rank', 'suit']) + +class FrenchDeck: + 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] +```` + +Esse objeto se comporta como uma **sequence** sem herdar de `list`. Ele implementa os métodos `__len__` e `__getitem__`, e por isso: + +* Pode ser iterado: `for card in deck: ...` +* Pode ser fatiado: `deck[12::13]` +* Funciona com `random.choice(deck)` +* Aceita `len(deck)` +* Funciona com `in`, `sorted`, etc. + +--- + +## ⚙️ O que acontece quando fazemos `len(deck)`? + +Python chama a função built-in len(), que é implementada em C (no código-fonte do CPython). + +### Se `deck` for um objeto built-in: + +* `len(deck)` chama a função C `PySequence_Size(obj)` +* Ela acessa `obj->ob_type->tp_as_sequence->sq_length` +* Esse campo (`sq_length`) aponta para uma função C como `list_length()` +* Resultado: chamada direta em C, altíssima performance + +### Se `deck` for um objeto definido em Python (como `FrenchDeck`): + +* Durante a criação da classe, o CPython detecta que ela tem `__len__` +* Cria um *wrapper C* chamado `slot_sq_length` +* Esse wrapper é armazenado em `tp_as_sequence->sq_length` +* Ao chamar `len(deck)`, o CPython executa: + + * `slot_sq_length(deck)` + * Que chama `deck.__len__()` por nome, uma única vez, e cacheia o acesso + +--- + +📍 Caminho interno percorrido pelo CPython: + +Etapa 1: Python chama função built-in +────────────────────────────────────── +len(deck) + └─► builtin_len(obj) ← função C em builtins.c + └─► PyObject_Length(obj) ← wrapper + └─► PySequence_Size(obj) + +Etapa 2: Acessa struct de tipo do objeto +────────────────────────────────────── +deck → PyObject + │ + └──► ob_type → PyTypeObject (FrenchDeck) + │ + └──► tp_as_sequence → PySequenceMethods + +Etapa 3: Verifica e chama o slot de comprimento +────────────────────────────────────── +PySequence_Size(obj) faz: + + if (tp_as_sequence && tp_as_sequence->sq_length) { + return tp_as_sequence->sq_length(obj); + } + +Ou seja: + + → lê ponteiro sq_length + → que aponta para slot_sq_length() + → chama slot_sq_length(obj) + +Etapa 4: slot_sq_length (função C wrapper) +────────────────────────────────────── +slot_sq_length(obj) faz: + + - Acessa atributo "__len__" na instância (só na 1ª vez) + - Chama obj.__len__() em Python + - Recebe PyObject (número) + - Converte para Py_ssize_t + - Retorna o valor + +Etapa 5: Execução do método Python +────────────────────────────────────── +def __len__(self): + return len(self._cards) + +Etapa 6: len(self._cards) +────────────────────────────────────── +Como self._cards é uma list, o caminho é: + +len(self._cards) + └─► PySequence_Size(list_obj) + └─► list_obj->ob_type->tp_as_sequence->sq_length + └─► list_length(list_obj) ← função C nativa + └─► return Py_SIZE(list_obj) + + +## 🔬 Estruturas internas no CPython + +### 🧱 `PyObject` + +Estrutura mínima de qualquer objeto: + +```c +typedef struct { + Py_ssize_t ob_refcnt; // contador de referências + PyTypeObject *ob_type; // ponteiro para o tipo +} PyObject; +``` + +--- + +### Slots C no CPython + + +**Slots** (como `sq_length`, `nb_add`, `mp_subscript`, etc.) são **campos dentro de structs em C** (como `PySequenceMethods`, `PyNumberMethods`, `PyMappingMethods`) que: + +* **Armazenam ponteiros para funções C**. +* Essas funções **implementam o comportamento** associado a cada parte do **Python Data Model**. + +--- + + 📌 Em outras palavras: + +> **Slots são ponteiros que apontam para funções C que implementam as operações definidas pelo data model.** + +Por exemplo: + +| Slot | Implementa | Quando é usado? | +| -------------- | ------------- | -------------------------- | +| `sq_length` | `len(obj)` | Quando se chama `len(obj)` | +| `sq_item` | `obj[i]` | Indexação | +| `nb_add` | `obj1 + obj2` | Soma | +| `mp_subscript` | `obj[key]` | Acesso via chave | +| `tp_call` | `obj()` | Objeto chamável | +| `tp_str` | `str(obj)` | Conversão para string | + +--- + +🎯 Exemplo: `sq_length` + +```c +typedef Py_ssize_t (*lenfunc)(PyObject *); + +typedef struct { + lenfunc sq_length; // ← slot + // outros slots omitidos +} PySequenceMethods; +``` + +Esse slot (`sq_length`) **aponta para** uma função como: + +* `slot_sq_length` (wrapper para `__len__`) +* ou `list_length` (implementação nativa para `list`) + + +### 🧠 `PyTypeObject` + +Representa o “tipo” (classe) de um objeto. + +Contém: + +* `tp_name`: nome do tipo +* `tp_basicsize`: tamanho da instância +* Ponteiros para funções C como `tp_init`, `tp_new`, `tp_dealloc` +* Ponteiros para **sub-estruturas** como: + + * `tp_as_sequence` → struct `PySequenceMethods` + * `tp_as_number` → struct `PyNumberMethods` + * `tp_as_mapping` → struct `PyMappingMethods` + +--- + +### 🧷 `PySequenceMethods` (sub-struct) + +```c +typedef struct { + lenfunc sq_length; // → usado por len() + binaryfunc sq_concat; // → usado por + + ssizeargfunc sq_repeat; // → usado por * + ssizeargfunc sq_item; // → usado por [] + // ... +} PySequenceMethods; +``` + +No caso de `FrenchDeck`, `sq_length` aponta para `slot_sq_length`. + +--- + +## 🧠 Conceitos importantes + +| Conceito | Explicação | +| ----------------- | ------------------------------------------------------------- | +| **CPython** | Implementação oficial da linguagem Python, escrita em C. | +| **Compilador** | Programa que traduz código para outro formato (ex: bytecode). | +| **Interpretador** | Executa o bytecode linha por linha (CPython faz os dois). | +| **PyObject** | Estrutura base para toda instância em Python. | +| **PyTypeObject** | Estrutura que representa a "classe" de um objeto em C. | +| **Slot C** | Campo da struct do tipo que guarda ponteiros para funções C. | +| **`sq_length`** | Slot usado para `len(obj)`. | +| **Bytecode** | Instruções intermediárias interpretadas pelo CPython. | + +--- + +## ✅ Conclusões + +* `len(obj)` em tipos embutidos (`list`, `str`) chama diretamente funções C via slot `sq_length`. +* `obj.__len__()` executa bytecode e envolve lookup, sendo mais lento e menos idiomático. +* Ao definir `__len__` numa classe Python, o CPython preenche automaticamente o slot C com um wrapper. +* **Slots C são fundamentais** para performance e integração entre objetos e a linguagem. + +--- + +## 📚 Fontes + +* [Fluent Python – Capítulo 1](https://www.oreilly.com/library/view/fluent-python/9781491946237/) +* [CPython source code – `typeobject.c`](https://github.com/python/cpython/blob/main/Objects/typeobject.c) +* [Documentação oficial: C API `PyTypeObject`](https://docs.python.org/3/c-api/typeobj.html) + +``` + +--- + +Etapa 1: Instância do objeto +──────────────────────────────────────────── + +-------------------+ +deck ──►| PyObject | ← instancia de FrenchDeck + | ob_refcnt | ← contador de referências + | ob_type ─────────┼─────► PyTypeObject (FrenchDeck) + +-------------------+ + +Etapa 2: Descrição do tipo (classe FrenchDeck) +─────────────────────────────────────────────── + +# tp_as_sequence é um ponteiro para uma struct do tipo PySequenceMethods +# Se o tipo implementa o sequence protocol (como __len__, __getitem__, etc), esse campo aponta para uma instância da struct PySequenceMethods. + + PyTypeObject (FrenchDeck) + +-----------------------------+ + | tp_name = "FrenchDeck" | + | tp_as_sequence ─────────┐ | + +--------------------------│--+ + ▼ +Etapa 3: Tabela de slots da sequência +──────────────────────────────────────────── + PySequenceMethods + +-----------------------------+ + | sq_length ─────────────┐ | + | sq_item | | + | sq_concat | | + +------------------------│----+ + ▼ +Etapa 4: Ponteiro para função C genérica +──────────────────────────────────────────── + slot_sq_length ← função C gerada pelo CPython + + // Implementação: + slot_sq_length(PyObject *self) { + // chama self.__len__() em Python + return _PyObject_CallMethodId(self, &PyId___len__, NULL); + } + +Etapa 5: Método Python definido pelo usuário +──────────────────────────────────────────── + def __len__(self): + return len(self._cards) + + self._cards = list → objeto embutido Python + +Etapa 6: Caminho interno até list_length +──────────────────────────────────────────── + self._cards ─► PyObject (tipo: list) + │ + └─► PyTypeObject (list) + │ + └─► tp_as_sequence → PySequenceMethods + │ + └─► sq_length = list_length + + list_length(PyObject *list) { + return Py_SIZE(list); + } diff --git a/01-data-model/data-model.ipynb b/01-data-model/data-model.ipynb index 5b30397..e63518b 100644 --- a/01-data-model/data-model.ipynb +++ b/01-data-model/data-model.ipynb @@ -92,6 +92,20 @@ "len(deck)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": 4, @@ -265,6 +279,7 @@ } ], "source": [ + "\n", "for card in deck:\n", " print(card)" ] diff --git a/01-data-model/frenchdeck.py b/01-data-model/frenchdeck.py index 47a0d05..8d8a65a 100644 --- a/01-data-model/frenchdeck.py +++ b/01-data-model/frenchdeck.py @@ -1,7 +1,31 @@ import collections +import random + +""" +2. O que é o "data model" em Python? +O data model é o conjunto de regras e interfaces que definem como objetos devem se comportar dentro da linguagem. + +🧩 Ele define: + +Como objetos são criados, comparados, iterados, somados, representados em string, etc. + +Como operadores (+, ==), funções built-in (len, abs, repr), estruturas de controle (for, with) interagem com objetos. + +➡️ O data model se baseia fortemente em special methods, como: + +| Python code | Internally calls... | +| -------------- | -------------------------------- | +| `len(obj)` | `obj.__len__()` | +| `obj[0]` | `obj.__getitem__(0)` | +| `obj + other` | `obj.__add__(other)` | +| `for x in obj` | `obj.__iter__()` | +| `with obj:` | `obj.__enter__()` / `__exit__()` | +""" Card = collections.namedtuple('Card', ['rank', 'suit']) +# Este exemplo mostra como você pode criar um objeto que se comporta como +# uma sequence (como uma lista). class FrenchDeck: ranks = [str(n) for n in range(2, 11)] + list('JQKA') suits = 'spades diamonds clubs hearts'.split() @@ -15,3 +39,68 @@ def __len__(self): def __getitem__(self, position): return self._cards[position] + + +# ############################################################## + +deck = FrenchDeck() + +# len(deck) funciona → porque implementamos __len__ +print(len(deck)) +# deck[0] funciona → porque implementamos __getitem__ +print(deck[0]) + +# ############################################################## + +random.choice(deck) # funciona porque o objeto é uma sequence + +# ############################################################## + +# O Python cuida dessas chamadas para manter consistência e otimização +# (por exemplo, len() em listas é uma chamada em C extremamente rápida). + +len(deck) # correto ✅ +deck.__len__() # errado ❌ + +""" +➡️ Em Python, um objeto é considerado uma sequence (mesmo que não herde de list) +se implementar: + +__getitem__(index) +__len__() + +Assim que você define esses dois special methods, seu objeto "comporta-se como" +uma sequence para qualquer código Python que use len() ou []. +""" + + + +# ############################################################## + +len(deck) # funciona → porque implementamos __len__ +deck[0], deck[-1] #→ porque implementamos __getitem__ +random.choice(deck) #→ funciona porque o objeto é uma sequence +for card in deck: #→ iteração implícita + deck[12::13] #→ slicing funciona + +# Esses comportamentos acontecem sem herdar de list ou implementar __iter__. +# Isso acontece porque o Python reconhece __getitem__ como sinal de que o objeto +# pode ser iterado. + +# ############################################################## + +""" +🧠 Reforçando: por que isso importa? +O data model define como fazer seu próprio tipo se comportar como os tipos built-in: + +listas → com __getitem__, __len__ + +números → com __add__, __mul__, __abs__ + +funções → com __call__ + +context managers → com __enter__, __exit__ + +iteradores → com __iter__, __next__ +""" + diff --git a/README.md b/README.md index 234c8da..d53cdc7 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,4 @@ Part / Chapter #|Title|Directory|1st ed. Chapter # 22|Dynamic Attributes and Properties|[22-dyn-attr-prop](22-dyn-attr-prop)|19 23|Attribute Descriptors|[23-descriptor](23-descriptor)|20 24|Class Metaprogramming|[24-class-metaprog](24-class-metaprog)|21 +