Skip to content

Latest commit

 

History

History
 
 

README.md

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.


# Estudo sobre o Python Data Model (com base no Fluent Python)

## 📖 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.

---

## 🧩 O que é o Python Data Model?

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`).

O modelo é expresso através dos **special methods** (também chamados de *dunder methods*), como:

- `__len__`
- `__getitem__`
- `__add__`
- `__call__`
- `__enter__`, `__exit__`
- `__iter__`, `__next__`

---

## 💡 Exemplo: `FrenchDeck`

```python
import collections

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:

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

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)

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


---

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);
        }