# 2. Definindo Tipos de Extensão: Tutorial

O Python permite que o desenvolvedor de um módulo de extensão em C defina novos tipos que podem ser manipulados a partir do código Python, da mesma forma que os tipos embutidos `str` e `list`. O código para de todos tipos de extensão segue um padrão, mas há alguns detalhes que você precisa entender antes de começar. Este documento é uma introdução suave ao tópico.

## 2.1. O básico

O tempo de execução do CPython trata todos os objetos Python como variáveis do tipo PyObject*, que funciona como um “tipo base” para todos os objetos Python. A estrutura `PyObject` em si contém apenas a contagem de referências do objeto e um ponteiro para o “objeto de tipo” correspondente. É nesse objeto de tipo que tudo acontece: ele determina quais funções em C o interpretador chama quando, por exemplo, um atributo é acessado em um objeto, um método é chamado ou o objeto é multiplicado por outro. Essas funções em C são chamadas de “métodos de tipo”.

Então, se você quiser definir um novo tipo de extensão, você precisa criar um novo objeto de tipo.

Esse tipo de coisa só pode ser explicado com exemplo, então aqui está um módulo mínimo, mas completo, que define um novo tipo nomeado `Custom` dentro de um módulo de extensão C `custom`:

Nota

O que estamos mostrando aqui é a maneira tradicional de definir tipos de extensão estáticos. Deve ser adequada para a maioria dos usos. A API C também permite definir tipos de extensão alocados em heap usando a função `PyType_FromSpec()`, que não é abordada neste tutorial.

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>

    typedef struct {
        PyObject_HEAD
        /* Campos específico do tipo vão aqui. */
    } CustomObject;

    static PyTypeObject CustomType = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom.Custom",
        .tp_doc = PyDoc_STR("Custom objects"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT,
        .tp_new = PyType_GenericNew,
    };

    static int
    custom_module_exec(PyObject *m)
    {
        if (PyType_Ready(&CustomType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot custom_module_slots[] = {
        {Py_mod_exec, custom_module_exec},
        // Só use isso quando estiver usando tipos estáticos
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef custom_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "custom",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = custom_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_custom(void)
    {
        return PyModuleDef_Init(&custom_module);
    }

Isso é bastante informação para assimilar de uma só vez, mas algumas partes devem parecer familiares pelo que foi visto no capítulo anterior. Este arquivo define três elementos:

1. O que um objeto `Custom` contém: esta é uma estrutura `CustomObject`, alocada uma vez para cada instância de `Custom`.

2. Como o tipo `Custom` se comporta: esta é a estrutura `CustomType`, que define um conjunto de sinalizadores e ponteiros de função que o interpretador inspeciona quando operações específicas são solicitadas.

3. Como definir e executar o módulo `custom`: esta é a função `PyInit_custom` e a estrutura associada `custom_module` para definir o módulo, e a função `custom_module_exec` para configurar um novo objeto de módulo.

O primeiro bit é

    typedef struct {
        PyObject_HEAD
    } CustomObject;

Isto é o que um objeto Custom conterá. `PyObject_HEAD` é obrigatório no início de cada estrutura de objeto e define um campo chamado `ob_base` do tipo `PyObject`, contendo um ponteiro para um objeto de tipo e uma contagem de referências (esses podem ser acessados usando os macros `Py_TYPE` e `Py_REFCNT`, respectivamente). O objetivo do macro é abstrair o layout e permitir campos adicionais em construções de depuração.

Nota

Não há ponto e vírgula acima após o macro `PyObject_HEAD`. Tenha cuidado para não adicionar um por engano: alguns compiladores podem emitir erros.

Obviamente, objetos geralmente armazenam dados adicionais além do padrão `PyObject_HEAD` boilerplate; por exemplo, aqui está a definição do padrão Python floats:

    typedef struct {
        PyObject_HEAD
        double ob_fval;
    } PyFloatObject;

O segundo bit é a definição do objeto de tipo.

    estático PyTypeObject CustomType = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom.Custom",
        .tp_doc = PyDoc_STR("Custom objeto"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT,
        .tp_new = PyType_GenericNew,
    };

Nota

Recomendamos usar inicializadores nomeados no estilo C99, como mostrado acima, para evitar listar todos os campos de `PyTypeObject` com os quais você não precisa se preocupar e também para não depender da ordem de declaração desses campos

A definição real de `PyTypeObject` no `object.h` possui muito mais campos do que a definição acima. Os campos restantes serão preenchidos com zeros pelo compilador C, e é prática comum não especificá-los explicitamente, a menos que sejam necessários.

Vamos separá-lo, um campo de cada vez

    .ob_base = PyVarObject_HEAD_INIT(NULL, 0)

Essa linha é um boilerplate obrigatório para inicializar o campo `ob_base` mencionado acima.

    .tp_name = "custom.Custom",

O nome do nosso tipo. Ele aparecerá na representação textual padrão do nosso objeto e em algumas mensagens erro, por exemplo:

    >>> "" + custom.Custom()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can only concatenate str (not "custom.Custom") to str

Observe que o nome é um nome pontilhado que inclui o nome módulo e o nome do tipo dentro do módulo. O módulo, neste caso, é `custom` e o tipo é `Custom`, portanto, definimos o nome do tipo como `custom.Custom`. O uso do pontilhado real caminho de importação é importante para tornar seu tipo compatível com o `pydoc` e os módulos `pickle`.

    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,

Isso serve para que o Python saiba quanta memória deve ser alocada ao criar novas instâncias do `Custom`. O `tp_itemsize` é usado somente para objeto de tamanho variável e, caso contrário, deve ser zero.

Nota

Se você quiser que seu tipo possa ser subclassificado a partir do Python, e seu tipo tiver o mesmo `tp_basicsize` que seu tipo base, você poderá ter problemas com herança múltipla. Uma subclasse Python do seu tipo terá de listar o seu tipo primeiro em `__bases__`, caso contrário ela não conseguirá chamar o método `__new__()` do seu tipo sem gerar um erro. Você pode evitar esse problema garantindo que seu tipo tenha um valor de `tp_basicsize` maior que o do tipo base. Na maior parte do tempo, isso já será verdadeiro, pois ou o tipo base será `object`, ou você estará adicionando membros de dados ao seu tipo base e, portanto, aumentando seu tamanho.

Definimos os sinalizadores da classe como `Py_TPFLAGS_DEFAULT`.

    .tp_flags = Py_TPFLAGS_DEFAULT,

Todos os tipos devem incluir essa constante em seus sinalizadores. Ela habilita todos os membros definidos até pelo menos o Python 3.3. Se você precisar de membros adicionais, será necessário fazer um OR com os sinalizadores correspondentes.

Fornecemos uma docstring para o tipo em tp_doc `tp_doc`.

    .tp_doc = PyDoc_STR("Custom objects"),

Para habilitar a criação de objetos, precisamos fornecer um manipulador para `tp_new`. Esse é o equivalente ao método Python `__new__()`, mas precisa ser especificado explicitamente. Neste caso, podemos simplesmente usar a implementação padrão fornecida pela função da API `PyType_GenericNew()`.

    .tp_new = PyType_GenericNew,

Todo o restante do arquivo deve ser familiar, exceto por alguns códigos em `custom_module_exec()`:

    se (PyType_Ready(&CustomType) < 0) {
        retorno -1;
    }

Isso inicializa o tipo `Custom`, preenchendo vários membros com os valores padrão apropriados, incluindo `ob_type`, que definimos inicialmente como `NULL`.

    if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
    return -1;
    }

Isso adiciona o tipo ao dicionário do módulo. Isso nos permite criar instâncias de `Custom` chamando a classe `Custom`:

    >>> import custom
    >>> mycustom = custom.Custom()

É isso! Só resta compilá-lo; coloque o código acima em um arquivo chamado `custom.c`,

    [build-system]
    requires = ["setuptools"]
    build-backend = "setuptools.build_meta"

    [project]
    name = "custom"
    version = "1"

Em um arquivo chamado `pyproject.toml`, e

    from setuptools import Extension, setup
    setup(ext_modules=[Extension("custom", ["custom.c"])])

Em um arquivo chamado `setup.py`; e então digitando

    $ python -m pip install .

no console deverá gerar um arquivo `custom.so` em um subdiretório e instalá-lo; agora abra o Python — você já deve conseguir `import custom` e experimentar objetos `Custom`.

Isso não foi tão difícil, foi?

Naturalmente, o tipo Custom atual é bastante desinteressante. Não tem dados e não faz nada. Não pode nem ser subclassificado.

## 2.2. Adicionando dados e métodos ao exemplo básico

Vamos estender o exemplo básico para adicionar alguns dados e métodos. Também vamos tornar o tipo utilizável como uma classe base. Iremos criar um novo módulo, `custom2`, que adiciona esses recursos:

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include <stddef.h> /* for offsetof() */

    typedef struct {
        PyObject_HEAD
        PyObject *first; /* first name */
        PyObject *last;  /* last name */
        int number;
    } CustomObject;

    static void
    Custom_dealloc(PyObject *op)
    {
        CustomObject *self = (CustomObject *) op;
        Py_XDECREF(self->first);
        Py_XDECREF(self->last);
        Py_TYPE(self)->tp_free(self);
    }

    static PyObject *
    Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        CustomObject *self;
        self = (CustomObject *) type->tp_alloc(type, 0);
        if (self != NULL) {
            self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
            if (self->first == NULL) {
                Py_DECREF(self);
                return NULL;
            }
            self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
            if (self->last == NULL) {
                Py_DECREF(self);
                return NULL;
            }
            self->number = 0;
        }
        return (PyObject *) self;
    }

    static int
    Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
    {
        CustomObject *self = (CustomObject *) op;
        static char *kwlist[] = {"first", "last", "number", NULL};
        PyObject *first = NULL, *last = NULL;

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
                                         &first, &last,
                                         &self->number))
            return -1;

        if (first) {
            Py_XSETREF(self->first, Py_NewRef(first));
        }
        if (last) {
            Py_XSETREF(self->last, Py_NewRef(last));
        }
        return 0;
    }

    static PyMemberDef Custom_members[] = {
        {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
         "first name"},
        {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
         "last name"},
        {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
    {
        CustomObject *self = (CustomObject *) op;
        if (self->first == NULL) {
            PyErr_SetString(PyExc_AttributeError, "first");
            return NULL;
        }
        if (self->last == NULL) {
            PyErr_SetString(PyExc_AttributeError, "last");
            return NULL;
        }
        return PyUnicode_FromFormat("%S %S", self->first, self->last);
    }

    static PyMethodDef Custom_methods[] = {
        {"name", Custom_name, METH_NOARGS,
         "Return the name, combining the first and last name"
        },
        {NULL}  /* Sentinel */
    };

    static PyTypeObject CustomType = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom2.Custom",
        .tp_doc = PyDoc_STR("Custom objects"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        .tp_new = Custom_new,
        .tp_init = Custom_init,
        .tp_dealloc = Custom_dealloc,
        .tp_members = Custom_members,
        .tp_methods = Custom_methods,
    };

    static int
    custom_module_exec(PyObject *m)
    {
        if (PyType_Ready(&CustomType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot custom_module_slots[] = {
        {Py_mod_exec, custom_module_exec},
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef custom_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "custom2",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = custom_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_custom2(void)
    {
        return PyModuleDef_Init(&custom_module);
    }

Esta versão do módulo possui várias alterações.

O tipo `Custom` agora possui três atributos de dados em sua estrutura C: first, last e number. As variáveis first e last são strings Python contendo o primeiro e o último nome. O atributo number é um inteiro em C.

A estrutura do objeto é atualizada de acordo

    typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last; /* last name */
    int number;
    } CustomObject;

Como agora temos dados para gerenciar, precisamos ter mais cuidado com a alocação e a desalocação do objeto. No mínimo, precisamos de método de desalocação:

    static void
    Custom_dealloc(PyObject *op)
    {
    CustomObject *self = (CustomObject *) op;
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free(self);
    }

que é atribuído ao membro `tp_dealloc`:

    tp_dealloc = Custom_dealloc,

Esse método primeiro limpa a contagem de referências dos dois atributos Python. `Py_XDECREF()` lida corretamente com o caso em que seu argumento é `NULL` (o que pode acontecer aqui se `tp_new` falhou no meio do processo). Em seguida, ele chama o membro `tp_free` do tipo do objeto (obtido por `Py_TYPE(self)`) para liberar a memória do objeto. Observe que o tipo do objeto pode não ser `CustomType`, pois o objeto pode ser uma instância de uma subclasse.

Nota

O cast explícito para `CustomObject *` acima é necessário porque definimos `Custom_dealloc` para receber um argumento do tipo `PyObject *`, enquanto o ponteiro de função `tp_dealloc` espera receber um argumento `PyObject *`. Ao atribuir nossa função ao slot `tp_dealloc` de um tipo, estamos declarando que ela só pode ser chamada com instâncias da classe `CustomObject`; portanto, o cast para `(CustomObject *)` é seguro. Isso é polimorfismo orientado a objetos, em C!

Em códigos existentes, ou em versões anteriores deste tutorial, você pode consultar funções semelhantes receberem diretamente um ponteiro para estrutura de objeto do subtipo (`CustomObject*`), assim:

    Custom_dealloc(CustomObject *self)
    {
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject *) self);
    }
    ...
    .tp_dealloc = (destructor) Custom_dealloc,

Isso faz a mesma coisa em todas as arquiteturas que o CPython oferece suporte, mas, de acordo com o padrão da linguagem C, isso invoca um comportamento indefinido.

Queremos nos certificar de que o primeiro e o último nome sejam inicializados como strings vazias, portanto, fornecemos uma implementação:: `tp_new`:

    static PyObject *
    Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
    CustomObject *self;
    self = (CustomObject *) type->tp_alloc(type, 0);
    if (self != NULL) {
    self->first = PyUnicode_FromString("");
    if (self->first == NULL) {
    Py_DECREF(self);
    return NULL;
    }
    self->last = PyUnicode_FromString("");
    if (self->last == NULL) {
    Py_DECREF(self);
    return NULL;
    }
    self->number = 0;
    }
    return (PyObject *) self;
    }

e instale-o no membro:: `tp_new`

    .tp_new = Custom_new,

O manipulador `tp_new` é responsável por criar (em oposição a inicializar) os objetos do tipo. Ele é exposto no Python como o método `__new__()`. Não é obrigatório definir um membro `tp_new` e, de fato, muitos tipos de extensão simplesmente reutilizam `PyType_GenericNew()`, como na primeira versão do tipo `Custom` acima. Neste caso, usamos o manipulador `tp_new` para inicializar os atributos `first` e `last` com valores padrão que não sejam `NULL`.

O `tp_new` recebe o tipo que está sendo instanciado (não necessariamente `CustomType`, caso uma subclasse esteja sendo instanciada) e quaisquer argumentos passados quando o tipo foi chamado, e deve retornar a instância criada. Manipuladores `tp_new` sempre aceitam argumentos posicionais e argumentos nomeados, mas frequentemente os ignoram, deixando o tratamento dos argumentos para os métodos inicializadores (ou seja, `tp_init` em C ou `__init__` em Python).

Nota

`tp_new` não deve chamar `tp_init` explicitamente, pois o interpretador fará isso por conta própria.

A implementação de `tp_new` chama o slot `tp_alloc` para alocar memória:

    self = (CustomObject *) type->tp_alloc(type, 0);

Como a alocação de memória pode falhar, precisamos verificar se o resultado de `tp_alloc` é `NULL` antes de prosseguir.

Nota

Nós não preenchemos o slot `tp_alloc` por conta própria. Em vez disso, `PyType_Ready()` o preenche herdando-o da nossa classe base, que por padrão é `object`. A maioria dos tipos usa a estratégia de alocação padrão.

Nota

Se você estiver criando um `tp_new` cooperativo (um que chama o `tp_new` ou `__new__()` de um tipo base), você não deve tentar determinar qual método chamar usando a ordem de resolução de métodos em tempo de execução. Sempre determine estaticamente qual tipo você vai chamar e chame seu `tp_new` diretamente ou via `type->tp_base->tp_new`. Se você não fizer isso, as subclasses Python do seu tipo que também herdam de outras classes definidas em Python podem não funcionar corretamente. (Especificamente, você pode não conseguir criar instâncias dessas subclasses sem receber um `TypeError`.)

Também definimos uma função de inicialização que aceita argumentos para fornecer valores iniciais para nosso instância:

    static intCustom_init(PyObject *op, PyObject *args, PyObject *kwds){ CustomObject *self = (CustomObject *) op; static char *kwlist[] = {"first", "last", "number", NULL}; PyObject *first = NULL, *last = NULL, *tmp; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist, &first, &last, &self->number)) return -1; if (first) { tmp = self->first; Py_INCREF(first); self->first = first; Py_XDECREF(tmp); } if (last) { tmp = self->last; Py_INCREF(last); self->last = last; Py_XDECREF(tmp); } return 0;}

preenchendo o slot :: `tp_init`.

    .tp_init = Custom_init,

O slot `tp_init` é exposto em Python como o método `__init__()`. Ele é usado para inicializar um objeto depois que ele é criado. Inicializadores sempre aceitam argumentos posicionais e argumentos nomeados, e devem retornar `0` em caso de sucesso ou `-1` em caso de erro.

Ao contrário do manipulador `tp_new`, não há garantia de que seja chamado (por exemplo, o módulo `pickle` por padrão não chama `__init__()` em instâncias desserializadas). Além disso, `tp_init` pode ser chamado várias vezes. Qualquer um pode chamar o método `__init__()` em nossos objetos. Por esse motivo, precisamos ter cuidado redobrado ao atribuir os novos valores de atributos. Poderíamos ficar tentados, por exemplo, a atribuir o membro `first` assim:

    if (first) { Py_XDECREF(self->first); Py_INCREF(first); self->first = first;}

Mas isso seria arriscado. Nosso tipo não restringe o tipo do membro `first`, então ele poderia ser qualquer tipo de objeto. Ele poderia ter um destrutor que executasse código que tentasse acessar o membro `first`; ou esse destrutor poderia desanexar o estado de thread e permitir que código arbitrário fosse executado em outras threads, acessando e modificando nosso objeto.

Para sermos extremamente cautelosos e nos proteger dessa possibilidade, quase sempre atribuímos novamente os membros antes de decrementar a contagem de referências. Quando não precisamos fazer isso?

- quando sabemos absolutamente que a contagem de referência é maior que 1;

- quando sabemos que a desalocação do objeto [1] não desvinculará o estado de thread nem causará quaisquer chamadas de volta no código do nosso tipo;

- quando estamos decrementando uma contagem de referências em um manipulador `tp_dealloc` de um tipo que não oferece suporte à coleta de lixo cíclica [2].

Queremos expor nossas variáveis de instância como atributos. Há várias maneiras de fazer isso. A forma mais simples é estabelecer definições de membros:

    static PyMemberDef Custom_members[] = { {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0, "first name"}, {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0, "last name"}, {"number", Py_T_INT, offsetof(CustomObject, number), 0, "custom number"}, {NULL} /* Sentinel */};

e colocar as definições no slot `tp_members`:

    .tp_members = Custom_members,

Cada definição de membro possui um nome de membro, um tipo, um deslocamento, sinalizadores de acesso e uma string de documentação. Consulte a seção Generic Attribute Management abaixo para mais detalhes.

Uma desvantagem dessa abordagem é que ela não fornece uma forma de restringir os tipos de objetos que podem ser atribuídos aos atributos em Python. Esperamos que os nomes first e last sejam strings, mas qualquer objeto Python pode ser atribuído. Além disso, os atributos podem ser excluídos, fazendo com que os ponteiros em C sejam definidos como `NULL`. Mesmo que possamos garantir que os membros sejam inicializados com valores não `NULL`, eles ainda podem acabar sendo definidos como `NULL` caso os atributos sejam apagados.

Definimos um único método,:meth:!Custom.name, que exibe o nome do objeto como a concatenação do primeiro nome com o último nome

    static PyObject *Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy)){ CustomObject *self = (CustomObject *) op; if (self->first == NULL) { PyErr_SetString(PyExc_AttributeError, "first"); return NULL; } if (self->last == NULL) { PyErr_SetString(PyExc_AttributeError, "last"); return NULL; } return PyUnicode_FromFormat("%S %S", self->first, self->last);}

O método é implementado como uma função em C que recebe uma instância de `Custom` (ou de uma subclasse de `Custom`) como primeiro argumento. Métodos sempre recebem a instância como primeiro argumento. Eles também costumam aceitar argumentos posicionais e nomeados, mas, neste caso, não recebemos nenhum e não precisamos aceitar uma tupla de argumentos posicionais nem um dicionário de argumentos nomeados. Este método equivale ao método Python:

    def name(self): return "%s %s" % (self.first, self.last)

Observe que precisamos verificar a possibilidade de que nossos atributos `first` e `last` estejam com valor `NULL`. Isso ocorre porque eles podem ser excluídos e, nesse caso, ficam definidos como `NULL`. Seria melhor impedir a exclusão desses atributos e restringir seus valores para que sejam sempre strings. Veremos como fazer isso na próxima seção.

Agora que definimos o método, precisamos criar uma array de definições de métodos:

    static PyMethodDef Custom_methods[] = {
    {"name", Custom_name, METH_NOARGS,
    "Return the name, combining the first and last name"
    },
    {NULL} /* Sentinel */
    };

note que usamos o sinalizador `METH_NOARGS` para indicar que o método não espera argumentos além de self)

e atribuí-lo para o slot:: `tp_methods`

    .tp_methods = Custom_methods,

Por fim, tornaremos nosso tipo utilizável como classe base para herança. Escrevemos nossos métodos com cuidado até agora, garantindo que eles não façam suposições sobre o tipo do objeto que está sendo criado ou utilizado; portanto, tudo o que precisamos fazer é adicionar a `Py_TPFLAGS_BASETYPE` à definição dos sinalizadores da nossa classe:

    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

Renomeamos `PyInit_custom()` para `PyInit_custom2()`, atualizamos o nome do módulo na estrutura `PyModuleDef`, e atualizamos o nome completo da classe na estrutura `PyTypeObject`.

Finalmente, atualizamos nosso arquivo `setup.py` para incluir o novo módulo,

    from setuptools import Extension, setupsetup(ext_modules=[ Extension("custom", ["custom.c"]), Extension("custom2", ["custom2.c"]),])

então podemos instalar novamente para realizarmos o `import custom2`

    $ python -m pip install .

## 2.3. Fornecendo controle mais preciso sobre atributos de dados

Nesta seção, forneceremos um controle mais preciso sobre como os atributos `first` e `last` são definidos no exemplo `Custom`. Na versão anterior do nosso módulo, as variáveis de instância `first` e `last` podiam receber valores que não fossem strings ou até mesmo ser excluídas. Queremos garantir que esses atributos sempre contenham strings.

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include <stddef.h> /* for offsetof() */

    typedef struct {
        PyObject_HEAD
        PyObject *first; /* first name */
        PyObject *last;  /* last name */
        int number;
    } CustomObject;

    static void
    Custom_dealloc(PyObject *op)
    {
        CustomObject *self = (CustomObject *) op;
        Py_XDECREF(self->first);
        Py_XDECREF(self->last);
        Py_TYPE(self)->tp_free(self);
    }

    static PyObject *
    Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        CustomObject *self;
        self = (CustomObject *) type->tp_alloc(type, 0);
        if (self != NULL) {
            self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
            if (self->first == NULL) {
                Py_DECREF(self);
                return NULL;
            }
            self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
            if (self->last == NULL) {
                Py_DECREF(self);
                return NULL;
            }
            self->number = 0;
        }
        return (PyObject *) self;
    }

    static int
    Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
    {
        CustomObject *self = (CustomObject *) op;
        static char *kwlist[] = {"first", "last", "number", NULL};
        PyObject *first = NULL, *last = NULL;

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                         &first, &last,
                                         &self->number))
            return -1;

        if (first) {
            Py_SETREF(self->first, Py_NewRef(first));
        }
        if (last) {
            Py_SETREF(self->last, Py_NewRef(last));
        }
        return 0;
    }

    static PyMemberDef Custom_members[] = {
        {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Custom_getfirst(PyObject *op, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        return Py_NewRef(self->first);
    }

    static int
    Custom_setfirst(PyObject *op, PyObject *value, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        if (value == NULL) {
            PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
            return -1;
        }
        if (!PyUnicode_Check(value)) {
            PyErr_SetString(PyExc_TypeError,
                            "The first attribute value must be a string");
            return -1;
        }
        Py_SETREF(self->first, Py_NewRef(value));
        return 0;
    }

    static PyObject *
    Custom_getlast(PyObject *op, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        return Py_NewRef(self->last);
    }

    static int
    Custom_setlast(PyObject *op, PyObject *value, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        if (value == NULL) {
            PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
            return -1;
        }
        if (!PyUnicode_Check(value)) {
            PyErr_SetString(PyExc_TypeError,
                            "The last attribute value must be a string");
            return -1;
        }
        Py_SETREF(self->last, Py_NewRef(value));
        return 0;
    }

    static PyGetSetDef Custom_getsetters[] = {
        {"first", Custom_getfirst, Custom_setfirst,
         "first name", NULL},
        {"last", Custom_getlast, Custom_setlast,
         "last name", NULL},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
    {
        CustomObject *self = (CustomObject *) op;
        return PyUnicode_FromFormat("%S %S", self->first, self->last);
    }

    static PyMethodDef Custom_methods[] = {
        {"name", Custom_name, METH_NOARGS,
         "Return the name, combining the first and last name"
        },
        {NULL}  /* Sentinel */
    };

    static PyTypeObject CustomType = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom3.Custom",
        .tp_doc = PyDoc_STR("Custom objects"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        .tp_new = Custom_new,
        .tp_init = Custom_init,
        .tp_dealloc = Custom_dealloc,
        .tp_members = Custom_members,
        .tp_methods = Custom_methods,
        .tp_getset = Custom_getsetters,
    };

    static int
    custom_module_exec(PyObject *m)
    {
        if (PyType_Ready(&CustomType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot custom_module_slots[] = {
        {Py_mod_exec, custom_module_exec},
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef custom_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "custom3",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = custom_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_custom3(void)
    {
        return PyModuleDef_Init(&custom_module);
    }

Para oferecer um controle maior sobre os atributos `first` e `last`, usaremos funções personalizadas de acesso (getters) e modificação (setters). A seguir estão as funções para obter e definir o atributo `first`:

    static PyObject *
    Custom_getfirst(PyObject *op, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        Py_INCREF(self->first);
        return self->first;
    }

    static int
    Custom_setfirst(PyObject *op, PyObject *value, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        PyObject *tmp;
        if (value == NULL) {
            PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
            return -1;
        }
        if (!PyUnicode_Check(value)) {
            PyErr_SetString(PyExc_TypeError,
                            "The first attribute value must be a string");
            return -1;
        }
        tmp = self->first;
        Py_INCREF(value);
        self->first = value;
        Py_DECREF(tmp);
        return 0;
    }

A função getter recebe um objeto `Custom` e um closure, que é um ponteiro para void. Neste caso, o closure é ignorado. (O closure oferece suporte a um modo de usar mais avançado, no qual dados de definição são passados ao getter e ao setter. Isso pode, por exemplo, permitir que um único conjunto de funções getter e setter decida qual atributo obter ou definir com base nos dados presentes no closure.)

A função setter recebe o objeto `Custom`, o novo valor e o closure. O novo valor pode ser `NULL`, caso em que o atributo está sendo excluído. No nosso setter, levantamos um erro se o atributo for excluído ou se o novo valor não for uma string.

Nós criamos um vetor de estruturas `PyGetSetDef`:

    static PyGetSetDef Custom_getsetters[] = {
        {"first", Custom_getfirst, Custom_setfirst,
         "first name", NULL},
        {"last", Custom_getlast, Custom_setlast,
         "last name", NULL},
        {NULL}  /* Sentinel */
    };

e registra isso num slot `tp_getset`:

    .tp_getset = Custom_getsetters,

A última entrada em uma estrutura `PyGetSetDef` é o “closure” mencionado acima. Neste caso, não estamos usando um closure, então simplesmente passamos `NULL`.

Também removemos as definições de membros para esses atributos:

    static PyMemberDef Custom_members[] = {
        {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };

Também precisamos atualizar o manipulador `tp_init` para permitir apenas strings [3] como valores passados:

    static int
    Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
    {
        CustomObject *self = (CustomObject *) op;
        static char *kwlist[] = {"first", "last", "number", NULL};
        PyObject *first = NULL, *last = NULL, *tmp;

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                         &first, &last,
                                         &self->number))
            return -1;

        if (first) {
            tmp = self->first;
            Py_INCREF(first);
            self->first = first;
            Py_DECREF(tmp);
        }
        if (last) {
            tmp = self->last;
            Py_INCREF(last);
            self->last = last;
            Py_DECREF(tmp);
        }
        return 0;
    }

Com essas alterações, podemos garantir que os membros `first` e `last` nunca sejam `NULL`, de modo que podemos remover quase todas as verificações de valores `NULL`. Isso significa que a maioria das chamadas para `Py_XDECREF()` pode ser substituída por chamadas para `Py_DECREF()`. O único lugar em que não podemos substituir essas chamadas é na implementação de `tp_dealloc`, onde existe a possibilidade de que a inicialização desses membros tenha falhado em `tp_new`.

Também renomeamos a função de inicialização do módulo e o nome do módulo dentro da função de inicialização, como fizemos anteriormente, e adicionamos uma definição extra ao arquivo `setup.py`.

## 2.4. Apoiando a coleta de lixo cíclica

O Python possui um coletor de lixo (GC) cíclico que pode identificar objetos que não são mais necessários mesmo quando suas contagens de referências não são zero. Isso pode acontecer quando os objetos participam de ciclos. Por exemplo, considere:

    >>> l = []
    >>> l.append(l)
    >>> del l

Neste exemplo, criamos uma lista que contém a si mesma. Quando a apagamos, ela ainda possui uma referência apontando para si própria. Sua contagem de referências não cai para zero. Felizmente, o coletor de lixo cíclico do Python acabará percebendo que a lista é lixo e irá liberá-la.

No segunda versão do exemplo de `Custom`, permitimos que qualquer tipo de objeto fosse armazenado nos atributos `first` ou `last` [4]. Além disso, na segunda e na terceira versões, permitimos estender `Custom`, e subclasses podem adicionar atributos arbitrários. Por qualquer um desses dois motivos, instâncias de `Custom` podem participar de ciclos:

    >>> import custom3
    >>> class Derived(custom3.Custom): pass
    ...
    >>> n = Derived()
    >>> n.some_attribute = n

Para permitir que uma instância de `Custom` que participa de um ciclo de referência seja corretamente detectada e coletada pelo coletor de lixo cíclico, nosso tipo `Custom` precisa preencher dois slots adicionais e ativar um sinalizador que habilita esses slots:

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include <stddef.h> /* for offsetof() */

    typedef struct {
        PyObject_HEAD
        PyObject *first; /* first name */
        PyObject *last;  /* last name */
        int number;
    } CustomObject;

    static int
    Custom_traverse(PyObject *op, visitproc visit, void *arg)
    {
        CustomObject *self = (CustomObject *) op;
        Py_VISIT(self->first);
        Py_VISIT(self->last);
        return 0;
    }

    static int
    Custom_clear(PyObject *op)
    {
        CustomObject *self = (CustomObject *) op;
        Py_CLEAR(self->first);
        Py_CLEAR(self->last);
        return 0;
    }

    static void
    Custom_dealloc(PyObject *op)
    {
        PyObject_GC_UnTrack(op);
        (void)Custom_clear(op);
        Py_TYPE(op)->tp_free(op);
    }

    static PyObject *
    Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
    {
        CustomObject *self;
        self = (CustomObject *) type->tp_alloc(type, 0);
        if (self != NULL) {
            self->first = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
            if (self->first == NULL) {
                Py_DECREF(self);
                return NULL;
            }
            self->last = Py_GetConstant(Py_CONSTANT_EMPTY_STR);
            if (self->last == NULL) {
                Py_DECREF(self);
                return NULL;
            }
            self->number = 0;
        }
        return (PyObject *) self;
    }

    static int
    Custom_init(PyObject *op, PyObject *args, PyObject *kwds)
    {
        CustomObject *self = (CustomObject *) op;
        static char *kwlist[] = {"first", "last", "number", NULL};
        PyObject *first = NULL, *last = NULL;

        if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                         &first, &last,
                                         &self->number))
            return -1;

        if (first) {
            Py_SETREF(self->first, Py_NewRef(first));
        }
        if (last) {
            Py_SETREF(self->last, Py_NewRef(last));
        }
        return 0;
    }

    static PyMemberDef Custom_members[] = {
        {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Custom_getfirst(PyObject *op, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        return Py_NewRef(self->first);
    }

    static int
    Custom_setfirst(PyObject *op, PyObject *value, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        if (value == NULL) {
            PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
            return -1;
        }
        if (!PyUnicode_Check(value)) {
            PyErr_SetString(PyExc_TypeError,
                            "The first attribute value must be a string");
            return -1;
        }
        Py_XSETREF(self->first, Py_NewRef(value));
        return 0;
    }

    static PyObject *
    Custom_getlast(PyObject *op, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        return Py_NewRef(self->last);
    }

    static int
    Custom_setlast(PyObject *op, PyObject *value, void *closure)
    {
        CustomObject *self = (CustomObject *) op;
        if (value == NULL) {
            PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
            return -1;
        }
        if (!PyUnicode_Check(value)) {
            PyErr_SetString(PyExc_TypeError,
                            "The last attribute value must be a string");
            return -1;
        }
        Py_XSETREF(self->last, Py_NewRef(value));
        return 0;
    }

    static PyGetSetDef Custom_getsetters[] = {
        {"first", Custom_getfirst, Custom_setfirst,
         "first name", NULL},
        {"last", Custom_getlast, Custom_setlast,
         "last name", NULL},
        {NULL}  /* Sentinel */
    };

    static PyObject *
    Custom_name(PyObject *op, PyObject *Py_UNUSED(dummy))
    {
        CustomObject *self = (CustomObject *) op;
        return PyUnicode_FromFormat("%S %S", self->first, self->last);
    }

    static PyMethodDef Custom_methods[] = {
        {"name", Custom_name, METH_NOARGS,
         "Return the name, combining the first and last name"
        },
        {NULL}  /* Sentinel */
    };

    static PyTypeObject CustomType = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom4.Custom",
        .tp_doc = PyDoc_STR("Custom objects"),
        .tp_basicsize = sizeof(CustomObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
        .tp_new = Custom_new,
        .tp_init = Custom_init,
        .tp_dealloc = Custom_dealloc,
        .tp_traverse = Custom_traverse,
        .tp_clear = Custom_clear,
        .tp_members = Custom_members,
        .tp_methods = Custom_methods,
        .tp_getset = Custom_getsetters,
    };

    static int
    custom_module_exec(PyObject *m)
    {
        if (PyType_Ready(&CustomType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "Custom", (PyObject *) &CustomType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot custom_module_slots[] = {
        {Py_mod_exec, custom_module_exec},
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef custom_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "custom4",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = custom_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_custom4(void)
    {
        return PyModuleDef_Init(&custom_module);
    }

Primeiro, o método de travessia informa ao coletor de lixo cíclico quais subobjetos podem participar de ciclos:

    static int
    Custom_traverse(PyObject *op, visitproc visit, void *arg)
    {
        CustomObject *self = (CustomObject *) op;
        int vret;
        if (self->first) {
            vret = visit(self->first, arg);
            if (vret != 0)
                return vret;
        }
        if (self->last) {
            vret = visit(self->last, arg);
            if (vret != 0)
                return vret;
        }
        return 0;
    }

Para cada subobjeto que pode participar de ciclos, precisamos chamar a função `visit()`, que é passada para o método de travessia. A função `visit()` recebe como argumentos o subobjeto e o argumento extra arg fornecido ao método de travessia. Ela retorna um valor inteiro que deve ser retornado se for diferente de zero.

O Python fornece o macro `Py_VISIT()`, que automatiza as chamadas às funções visit. Com `Py_VISIT()`, podemos minimizar a quantidade de boilerplate em `Custom_traverse`:

    static int
    Custom_traverse(PyObject *op, visitproc visit, void *arg)
    {
        CustomObject *self = (CustomObject *) op;
        Py_VISIT(self->first);
        Py_VISIT(self->last);
        return 0;
    }

Nota

A implementação de `tp_traverse` deve nomear seus argumentos exatamente como visit e arg para que seja possível usar `Py_VISIT()`.

Segundo, precisamos fornecer um método para limpar quaisquer subobjetos que possam participar de ciclos:

    static int
    Custom_clear(PyObject *op)
    {
        CustomObject *self = (CustomObject *) op;
        Py_CLEAR(self->first);
        Py_CLEAR(self->last);
        return 0;
    }

Observe o uso do macro `Py_CLEAR()`. Ele é a forma recomendada e segura de limpar atributos de dados de tipos arbitrários enquanto decrementa suas contagens de referências. Se você chamasse `Py_XDECREF()` no atributo antes de defini-lo como `NULL`, haveria a possibilidade de que o destrutor do atributo chamasse novamente algum código que lesse o atributo (especialmente se houver um ciclo de referência).

Nota

Você poderia emular `Py_CLEAR()` ao escrever:

    PyObject *tmp;
    tmp = self->first;
    self->first = NULL;
    Py_XDECREF(tmp);

Apesar disso, é muito mais fácil e menos propenso a erros, usar sempre `Py_CLEAR()` ao excluir um atributo. Não tente micro‐otimizar às custas da robustez!

O desalocador `Custom_dealloc` pode executar código arbitrário ao limpar atributos. Isso significa que o GC pode ser acionado dentro da função. Como o GC presume que a contagem de referências não é zero, precisamos remover o objeto do rastreamento do GC chamando `PyObject_GC_UnTrack()` antes de limpar os membros. A seguir está nossa versão reimplementada do desalocador usando `PyObject_GC_UnTrack()` e `Custom_clear`:

    static void
    Custom_dealloc(PyObject *op)
    {
        PyObject_GC_UnTrack(op);
        (void)Custom_clear(op);
        Py_TYPE(op)->tp_free(op);
    }

Por fim, adicionamos o macro `Py_TPFLAGS_HAVE_GC` aos sinalizadores da classe:

    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,

Isso é praticamente tudo. Se tivéssemos escrito manipuladores personalizados para `tp_alloc` ou `tp_free`, precisaríamos adaptá-los para a coleta de lixo cíclica. A maior parte das extensões usará as versões fornecidas automaticamente.

## 2.5. Criando subclasses de outros tipos

É possível criar novos tipos de extensão derivados de tipos já existentes. É mais fácil herdar dos tipos embutidos, já que uma extensão pode usar diretamente o `PyTypeObject` de que precisa. Pode ser difícil compartilhar essas estruturas `PyTypeObject` entre módulos de extensão distintos.

Neste exemplo, criaremos um tipo `SubList` que herda do tipo embutido `list`. O novo tipo será totalmente compatível com listas comuns, mas terá um método adicional, `increment()`, que incrementa um contador interno:

    >>> import sublist
    >>> s = sublist.SubList(range(3))
    >>> s.extend(s)
    >>> print(len(s))
    6
    >>> print(s.increment())
    1
    >>> print(s.increment())
    2

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>

    typedef struct {
        PyListObject list;
        int state;
    } SubListObject;

    static PyObject *
    SubList_increment(PyObject *op, PyObject *Py_UNUSED(dummy))
    {
        SubListObject *self = (SubListObject *) op;
        self->state++;
        return PyLong_FromLong(self->state);
    }

    static PyMethodDef SubList_methods[] = {
        {"increment", SubList_increment, METH_NOARGS,
         PyDoc_STR("increment state counter")},
        {NULL},
    };

    static int
    SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
    {
        SubListObject *self = (SubListObject *) op;
        if (PyList_Type.tp_init(op, args, kwds) < 0)
            return -1;
        self->state = 0;
        return 0;
    }

    static PyTypeObject SubListType = {
        .ob_base = PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "sublist.SubList",
        .tp_doc = PyDoc_STR("SubList objects"),
        .tp_basicsize = sizeof(SubListObject),
        .tp_itemsize = 0,
        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
        .tp_init = SubList_init,
        .tp_methods = SubList_methods,
    };

    static int
    sublist_module_exec(PyObject *m)
    {
        SubListType.tp_base = &PyList_Type;
        if (PyType_Ready(&SubListType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
            return -1;
        }

        return 0;
    }

    static PyModuleDef_Slot sublist_module_slots[] = {
        {Py_mod_exec, sublist_module_exec},
        {Py_mod_multiple_interpreters, Py_MOD_MULTIPLE_INTERPRETERS_NOT_SUPPORTED},
        {0, NULL}
    };

    static PyModuleDef sublist_module = {
        .m_base = PyModuleDef_HEAD_INIT,
        .m_name = "sublist",
        .m_doc = "Example module that creates an extension type.",
        .m_size = 0,
        .m_slots = sublist_module_slots,
    };

    PyMODINIT_FUNC
    PyInit_sublist(void)
    {
        return PyModuleDef_Init(&sublist_module);
    }

Como você pode consultar, o código-fonte se assemelha bastante aos exemplos de `Custom` das seções anteriores. Vamos detalhar as principais diferenças entre eles.

    typedef struct {
        PyListObject list;
        int state;
    } SubListObject;

A diferença principal para objetos de tipos derivados é que a estrutura de objeto do tipo base deve ser o primeiro valor. O tipo base já inclui o `PyObject_HEAD()` no início de sua própria estrutura.

Quando um objeto Python é uma instância de `SubList`, seu ponteiro `PyObject *` pode ser convertido com segurança tanto para `PyListObject *` quanto para `SubListObject *`:

    static int
    SubList_init(PyObject *op, PyObject *args, PyObject *kwds)
    {
        SubListObject *self = (SubListObject *) op;
        if (PyList_Type.tp_init(op, args, kwds) < 0)
            return -1;
        self->state = 0;
        return 0;
    }

Vemos acima como chamar o método `__init__()` do tipo base.

Esse padrão é importante ao escrever um tipo que possui membros personalizados em `tp_new` e `tp_dealloc`. O manipulador `tp_new` não deve realmente criar a memória do objeto usando o seu próprio `tp_alloc`; em vez disso, deve permitir que a classe base faça isso, chamando o seu próprio `tp_new`.

A estrutura `PyTypeObject` provê o campo `tp_base`, que especifica a classe base concreta do tipo. Devido a problemas de compilação multiplataforma, não se deve preencher esse campo diretamente com uma referência para `PyList_Type`; isso deve ser feito dentro da função `Py_mod_exec`:

    static int
    sublist_module_exec(PyObject *m)
    {
        SubListType.tp_base = &PyList_Type;
        if (PyType_Ready(&SubListType) < 0) {
            return -1;
        }

        if (PyModule_AddObjectRef(m, "SubList", (PyObject *) &SubListType) < 0) {
            return -1;
        }

        return 0;
    }

Antes de chamar `PyType_Ready()`, a estrutura do tipo deve ter o slot `tp_base` preenchido. Ao derivarmos um tipo existente, não é necessário preencher o slot `tp_alloc` com `PyType_GenericNew()` — a função de alocação do tipo base será herdada.

Depois disso, chamar `PyType_Ready()` e adicionar o objeto de tipo ao módulo é o mesmo processo usado nos exemplos básicos com `Custom`.

Notas de rodapé

[1]

Isso é verdade quando sabemos que o objeto é um tipo básico, como uma string ou um float.

[2]

Confiamos nisso no manipulador `tp_dealloc` neste exemplo, porque o nosso tipo não oferece suporte à coleta de lixo.

[3]

Agora sabemos que o primeiro e último membros são strings, então talvez pudéssemos ter menos cuidado com a diminuição de suas contagens de referência, no entanto, aceitamos instâncias de subclasses de string. Mesmo que a desalocação de strings normais não retorne aos nossos objetos, não podemos garantir que a desalocação de uma instância de uma subclasse de cadeias de caracteres não retornará aos nossos objetos.

[4]

Além disso, mesmo com nossos atributos restritos a instâncias de strings, o usuário poderia passar arbitrariamente subclasses `str` e, portanto, ainda criar ciclos de referência.