# 2. Definición de tipos de extensión: Tutorial

Python le permite al escritor de un módulo de extensión C definir nuevos tipos que pueden ser manipulados desde el código Python, al igual que los tipos incorporados `str` y `list`. El código para todos los tipos de extensión sigue un patrón, pero hay algunos detalles que debe comprender antes de comenzar. Este documento es una introducción suave al tema.

## 2.1. Lo Básico

El tiempo de ejecución CPython ve todos los objetos de Python como variables de tipo `PyObject*`, que sirve como un «tipo base» para todos los objetos de Python. La estructura `PyObject` solo contiene el reference count del objeto y un puntero al «objeto tipo» del objeto. Aquí es donde está la acción; el objeto tipo determina qué funciones (C) llama el intérprete cuando, por ejemplo, se busca un atributo en un objeto, se llama un método o se multiplica por otro objeto. Estas funciones de C se denominan «métodos de tipo».

Por lo tanto, si desea definir un nuevo tipo de extensión, debe crear un nuevo objeto de tipo.

Este tipo de cosas solo se pueden explicar con un ejemplo, por lo que aquí hay un módulo mínimo, pero completo, que define un nuevo tipo llamado `Custom` dentro de un módulo de extensión C `custom`:

Nota

Lo que estamos mostrando aquí es la forma tradicional de definir tipos de extensión estáticos. Debe ser adecuado para la mayoría de los usos. La API de C también permite definir tipos de extensiones asignadas en el montón utilizando la función `PyType_FromSpec()`, que no se trata en este tutorial.

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>

    typedef struct {
        PyObject_HEAD
        /* Type-specific fields go here. */
    } CustomObject;

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

    static PyModuleDef custommodule = {
        PyModuleDef_HEAD_INIT,
        .m_name = "custom",
        .m_doc = "Example module that creates an extension type.",
        .m_size = -1,
    };

    PyMODINIT_FUNC
    PyInit_custom(void)
    {
        PyObject *m;
        if (PyType_Ready(&CustomType) < 0)
            return NULL;

        m = PyModule_Create(&custommodule);
        if (m == NULL)
            return NULL;

        Py_INCREF(&CustomType);
        if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
            Py_DECREF(&CustomType);
            Py_DECREF(m);
            return NULL;
        }

        return m;
    }

Ahora, eso es bastante para asimilar a la vez, pero espero que los fragmentos le resulten familiares del capítulo anterior. Este archivo define tres cosas:

1. Lo que contiene un objeto `Custom`: esta es la estructura `CustomObject`, que se asigna una vez para cada instancia de `Custom`.

2. Cómo se comporta `Custom` type: esta es la estructura `CustomType`, que define un conjunto de indicadores y punteros de función que el intérprete inspecciona cuando se solicitan operaciones específicas.

3. Cómo inicializar el módulo `custom`: esta es la función `PyInit_custom` y la estructura asociada `custommodule`.

La primera parte es:

    typedef struct {
        PyObject_HEAD
    } CustomObject;

Esto es lo que contendrá un objeto personalizado. `PyObject_HEAD` es obligatorio al comienzo de cada estructura de objeto y define un campo llamado `ob_base` de tipo `PyObject`, que contiene un puntero a un objeto de tipo y un recuento de referencia (estos pueden ser accedidos mediante las macros `Py_REFCNT` y `Py_TYPE` respectivamente). El motivo de la macro es abstraer el diseño y habilitar campos adicionales en las compilaciones de depuración.

Nota

No hay punto y coma (;) arriba después de la macro `PyObject_HEAD`. Tenga cuidado de agregar uno por accidente: algunos compiladores se quejarán.

Por supuesto, los objetos generalmente almacenan datos adicionales además del estándar `PyObject_HEAD` repetitivo; por ejemplo, aquí está la definición de puntos flotantes del estándar de Python:

    typedef struct {
        PyObject_HEAD
        double ob_fval;
    } PyFloatObject;

La segunda parte es la definición del tipo de objecto.

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

Nota

Recomendamos utilizar los inicializadores designados al estilo C99 como se indica arriba, para evitar enumerar todos los campos `PyTypeObject` que no le interesan y también para evitar preocuparse por el orden de declaración de los campos.

La definición real de `PyTypeObject` en `object.h` tiene muchos más campos que la definición anterior. El compilador de C rellenará los campos restantes con ceros, y es una práctica común no especificarlos explícitamente a menos que los necesite.

Lo vamos a separar, un campo a la vez:

    PyVarObject_HEAD_INIT(NULL, 0)

Esta línea es obligatoria para inicializar el campo `ob_base` mencionado anteriormente.

    .tp_name = "custom.Custom",

El nombre de nuestro tipo. Esto aparecerá en la representación textual predeterminada de nuestros objetos y en algunos mensajes de error, por ejemplo:

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

Tenga en cuenta que el nombre es un nombre punteado que incluye tanto el nombre del módulo como el nombre del tipo dentro del módulo. El módulo en este caso es `custom` y el tipo es `Custom`, por lo que establecemos el nombre del tipo en `custom.Custom`. Usar la ruta de importación punteada real es importante para que su tipo sea compatible con los módulos `pydoc` y `pickle`.

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

Esto es para que Python sepa cuánta memoria asignar al crear instancias nuevas `Custom`. `tp_itemsize` solo se usa para objetos de tamaño variable y, de lo contrario, debería ser cero.

Nota

Si desea que su tipo pueda tener subclases desde Python, y su tipo tiene el mismo `tp_basicsize` como su tipo base, puede tener problemas con la herencia múltiple. Una subclase de Python de su tipo tendrá que enumerar su tipo primero en su `__bases__`, o de lo contrario no podrá llamar al método de su tipo `__new__()` sin obtener un error. Puede evitar este problema asegurándose de que su tipo tenga un valor mayor para `tp_basicsize` que su tipo base. La mayoría de las veces, esto será cierto de todos modos, porque su tipo base será `object`, o de lo contrario agregará miembros de datos a su tipo base y, por lo tanto, aumentará su tamaño.

Configuramos las banderas de clase a `Py_TPFLAGS_DEFAULT`.

    .tp_flags = Py_TPFLAGS_DEFAULT,

Todos los tipos deben incluir esta constante en sus banderas. Habilita todos los miembros definidos hasta al menos Python 3.3. Si necesita más miembros, necesitará O (OR) las banderas correspondientes.

Proporcionamos una cadena de documentos para el tipo en `tp_doc`.

    .tp_doc = "Custom objects",

Para habilitar la creación de objetos, debemos proporcionar un controlador `tp_new`. Este es el equivalente del método Python `__new__()`, pero debe especificarse explícitamente. En este caso, podemos usar la implementación predeterminada proporcionada por la función API `PyType_GenericNew()`.

    .tp_new = PyType_GenericNew,

Todo lo demás en el archivo debe ser familiar, excepto algún código en `PyInit_custom()`:

    if (PyType_Ready(&CustomType) < 0)
        return;

Esto inicializa el tipo `Custom`, completando un número de miembros con los valores predeterminados apropiados, que incluyen `ob_type` que inicialmente configuramos en `NULL`.

    Py_INCREF(&CustomType);
    if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
        Py_DECREF(&CustomType);
        Py_DECREF(m);
        return NULL;
    }

Esto agrega el tipo al diccionario del módulo. Esto nos permite crear instancias `Custom` llamando la clase `Custom`:

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

¡Eso es! Todo lo que queda es construirlo; ponga el código anterior en un archivo llamado `custom.c` y:

    from distutils.core import setup, Extension
    setup(name="custom", version="1.0",
          ext_modules=[Extension("custom", ["custom.c"])])

en un archivo llamado `setup.py`; luego escribiendo

    $ python setup.py build

en un shell debería producir un archivo `custom.so` en un subdirectorio; muévete a ese directorio y abre Python — deberías poder `import custom` y jugar con objetos personalizados.

Eso no fue tan difícil, ¿verdad?

Por supuesto, el tipo personalizado actual es bastante poco interesante. No tiene datos y no hace nada. Ni siquiera se puede subclasificar.

Nota

Si bien esta documentación muestra el módulo estándar `distutils` para construir extensiones C, se recomienda en casos de uso del mundo real utilizar la biblioteca `setuptools` más nueva y mejor mantenida. La documentación sobre cómo hacer esto está fuera del alcance de este documento y se puede encontrar en la Guía de usuario del Empaquetamiento de Python.

## 2.2. Agregar datos y métodos al ejemplo básico

Extendamos el ejemplo básico para agregar algunos datos y métodos. También hagamos que el tipo sea utilizable como una clase base. Crearemos un nuevo módulo, `custom2` que agrega estas capacidades:

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include "structmember.h"

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

    static void
    Custom_dealloc(CustomObject *self)
    {
        Py_XDECREF(self->first);
        Py_XDECREF(self->last);
        Py_TYPE(self)->tp_free((PyObject *) 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 = 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;
    }

    static int
    Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
    {
        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;
    }

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

    static PyObject *
    Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
    {
        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", (PyCFunction) Custom_name, METH_NOARGS,
         "Return the name, combining the first and last name"
        },
        {NULL}  /* Sentinel */
    };

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

    static PyModuleDef custommodule = {
        PyModuleDef_HEAD_INIT,
        .m_name = "custom2",
        .m_doc = "Example module that creates an extension type.",
        .m_size = -1,
    };

    PyMODINIT_FUNC
    PyInit_custom2(void)
    {
        PyObject *m;
        if (PyType_Ready(&CustomType) < 0)
            return NULL;

        m = PyModule_Create(&custommodule);
        if (m == NULL)
            return NULL;

        Py_INCREF(&CustomType);
        if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
            Py_DECREF(&CustomType);
            Py_DECREF(m);
            return NULL;
        }

        return m;
    }

Esta versión del módulo tiene una serie de cambios.

Hemos agregado una inclusión adicional:

    #include <structmember.h>

Esto incluye declaraciones que usamos para manejar atributos, como se describe un poco más adelante.

El tipo `Custom` ahora tiene tres atributos de datos en su estructura C, first, last y number. Las variables first y last son cadenas de caracteres de Python que contienen nombres y apellidos. El atributo number es un entero C.

La estructura del objeto se actualiza en consecuencia:

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

Debido a que ahora tenemos datos para administrar, debemos ser más cuidadosos con la asignación de objetos y la desasignación. Como mínimo, necesitamos un método de desasignación:

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

que se asigna al miembro `tp_dealloc`:

    .tp_dealloc = (destructor) Custom_dealloc,

Este método primero borra los recuentos de referencia de los dos atributos de Python. `Py_XDECREF()` maneja correctamente el caso donde su argumento es `NULL` (lo que podría ocurrir aquí si `tp_new` fallara a mitad de camino). Luego llama al miembro `tp_free` del tipo de objeto (calculado por `Py_TYPE(self)`) para liberar la memoria del objeto. Tenga en cuenta que el tipo de objeto podría no ser `CustomType`, porque el objeto puede ser una instancia de una subclase.

Nota

La conversión explícita a `destructor` anterior es necesaria porque definimos `Custom_dealloc` para tomar un argumento `CustomObject *`, pero el puntero de función `tp_dealloc` espera recibir un argumento `PyObject *`. De lo contrario, el compilador emitirá una advertencia. Este es un polimorfismo orientado a objetos, en C!

Queremos asegurarnos de que el nombre y el apellido se inicialicen en cadenas de caracteres vacías, por lo que proporcionamos una implementación `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 instalarlo en el miembro `tp_new`:

    .tp_new = Custom_new,

El controlador `tp_new` es responsable de crear (en lugar de inicializar) objetos del tipo. Está expuesto en Python como el método `__new__()`. No es necesario definir un miembro `tp_new`, y de hecho muchos tipos de extensiones simplemente reutilizarán `PyType_GenericNew()` como se hizo en la primera versión del tipo `Personalizado` anterior. En este caso, usamos el controlador `tp_new` para inicializar los atributos `first` y `last` a valores predeterminados que no sean `NULL`.

`tp_new` se pasa el tipo que se instancia (no necesariamente `CustomType`, si se instancia una subclase) y cualquier argumento pasado cuando se llamó al tipo, y se espera que retorna la instancia creada. Los manejadores `tp_new` siempre aceptan argumentos posicionales y de palabras clave, pero a menudo ignoran los argumentos, dejando el manejo de argumentos al inicializador (también conocido como, `tp_init` en C o `__init__` en Python).

Nota

`tp_new` no debería llamar explícitamente a `tp_init`, ya que el intérprete lo hará por sí mismo.

La implementación `tp_new` llama al `tp_alloc` para asignar memoria:

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

Como la asignación de memoria puede fallar, debemos verificar el resultado `tp_alloc` contra `NULL` antes de continuar.

Nota

No llenamos la ranura `tp_alloc` nosotros mismos. Más bien `PyType_Ready()` lo llena para nosotros al heredarlo de nuestra clase base, que es `object` por defecto. La mayoría de los tipos utilizan la estrategia de asignación predeterminada.

Nota

Si está creando una cooperativa `tp_new` (una que llama a un tipo base `tp_new` o `__new__()`), no debe intentar determinar a qué método llamar utilizando el orden de resolución del método en tiempo de ejecución. Siempre determine estáticamente a qué tipo va a llamar, y llame a su `tp_new` directamente, o mediante `type->tp_base->tp_new`. Si no hace esto, las subclases de Python de su tipo que también heredan de otras clases definidas por Python pueden no funcionar correctamente. (Específicamente, es posible que no pueda crear instancias de tales subclases sin obtener un `TypeError`).

También definimos una función de inicialización que acepta argumentos para proporcionar valores iniciales para nuestra instancia:

    static int
    Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
    {
        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;
    }

rellenando la ranura `tp_init`.

    .tp_init = (initproc) Custom_init,

La ranura `tp_init` está expuesta en Python como el método `__init__()`. Se utiliza para inicializar un objeto una vez creado. Los inicializadores siempre aceptan argumentos posicionales y de palabras clave, y deben retornar `0` en caso de éxito o `-1` en caso de error.

A diferencia del controlador `tp_new`, no hay garantía de que se llame a `tp_init` (por ejemplo, el módulo `pickle` por defecto no llama a `__init__()` en instancias no controladas ) También se puede llamar varias veces. Cualquiera puede llamar al método `__init__()` en nuestros objetos. Por esta razón, debemos tener mucho cuidado al asignar los nuevos valores de atributo. Podríamos sentirnos tentados, por ejemplo, a asignar el `primer` miembro de esta manera:

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

But this would be risky. Our type doesn’t restrict the type of the `first` member, so it could be any kind of object. It could have a destructor that causes code to be executed that tries to access the `first` member; or that destructor could release the Global interpreter Lock and let arbitrary code run in other threads that accesses and modifies our object.

Para ser paranoicos y protegernos de esta posibilidad, casi siempre reasignamos miembros antes de disminuir sus recuentos de referencias. ¿Cuándo no tenemos que hacer esto?

- cuando sabemos absolutamente que el recuento de referencia es mayor que 1;

- cuando sabemos que la desasignación del objeto 1 no liberará el GIL ni causará ninguna llamada al código de nuestro tipo;

- al disminuir un recuento de referencias en un manejador `tp_dealloc` en un tipo que no admite la recolección de basura cíclica 2.

Queremos exponer nuestras variables de instancia como atributos. Hay varias formas de hacerlo. La forma más simple es definir definiciones de miembros:

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

y poner las definiciones en la ranura `tp_members`:

    .tp_members = Custom_members,

Cada definición de miembro tiene un nombre de miembro, tipo, desplazamiento, banderas de acceso y cadena de caracteres de documentación. Consulte la sección Gestión de atributos genéricos a continuación para obtener más detalles.

Una desventaja de este enfoque es que no proporciona una forma de restringir los tipos de objetos que se pueden asignar a los atributos de Python. Esperamos que el nombre y el apellido sean cadenas, pero se pueden asignar objetos de Python. Además, los atributos se pueden eliminar, configurando los punteros C en `NULL`. Si bien podemos asegurarnos de que los miembros se inicialicen en valores que no sean `NULL`, los miembros se pueden establecer en `NULL` si se eliminan los atributos.

Definimos un método único, `Custom.name()`, que genera el nombre de los objetos como la concatenación de los nombres y apellidos.

    static PyObject *
    Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
    {
        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);
    }

El método se implementa como una función C que toma una instancia de `Custom` (o subclase `Custom`) como primer argumento. Los métodos siempre toman una instancia como primer argumento. Los métodos a menudo también toman argumentos posicionales y de palabras clave, pero en este caso no tomamos ninguno y no necesitamos aceptar una tupla de argumentos posicionales o un diccionario de argumentos de palabras clave. Este método es equivalente al método Python:

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

Tenga en cuenta que tenemos que verificar la posibilidad de que nuestros miembros `first` y `last` sean `NULL`. Esto se debe a que se pueden eliminar, en cuyo caso se establecen en `NULL`. Sería mejor evitar la eliminación de estos atributos y restringir los valores de los atributos para que sean cadenas de caracteres. Veremos cómo hacerlo en la siguiente sección.

Ahora que hemos definido el método, necesitamos crear un arreglo de definiciones de métodos:

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

(tenga en cuenta que usamos el indicador `METH_NOARGS` para indicar que el método no espera argumentos distintos de self)

y asignarlo a la ranura `tp_methods`:

    .tp_methods = Custom_methods,

Finalmente, haremos que nuestro tipo sea utilizable como una clase base para la subclase. Hemos escrito nuestros métodos cuidadosamente hasta ahora para que no hagan suposiciones sobre el tipo de objeto que se está creando o utilizando, por lo que todo lo que tenemos que hacer es agregar `Py_TPFLAGS_BASETYPE` a nuestra definición de bandera de clase:

    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

Cambiamos el nombre de `PyInit_custom()` a `PyInit_custom2()`, actualizamos el nombre del módulo en la estructura `PyModuleDef` y actualizamos el nombre completo de la clase en la estructura `PyTypeObject`.

Finalmente, actualizamos nuestro archivo `setup.py` para construir el nuevo módulo:

    from distutils.core import setup, Extension
    setup(name="custom", version="1.0",
          ext_modules=[
             Extension("custom", ["custom.c"]),
             Extension("custom2", ["custom2.c"]),
             ])

## 2.3. Proporcionar un control más preciso sobre los atributos de datos

En esta sección, proporcionaremos un control más preciso sobre cómo se establecen los atributos `first` y `last` en el ejemplo `Custom`. En la versión anterior de nuestro módulo, las variables de instancia `first` y `last` podrían establecerse en valores que no sean de cadena o incluso eliminarse. Queremos asegurarnos de que estos atributos siempre contengan cadenas.

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include "structmember.h"

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

    static void
    Custom_dealloc(CustomObject *self)
    {
        Py_XDECREF(self->first);
        Py_XDECREF(self->last);
        Py_TYPE(self)->tp_free((PyObject *) 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 = 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;
    }

    static int
    Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
    {
        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;
    }

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

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

    static int
    Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
    {
        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;
    }

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

    static int
    Custom_setlast(CustomObject *self, PyObject *value, void *closure)
    {
        PyObject *tmp;
        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;
        }
        tmp = self->last;
        Py_INCREF(value);
        self->last = value;
        Py_DECREF(tmp);
        return 0;
    }

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

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

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

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

    static PyModuleDef custommodule = {
        PyModuleDef_HEAD_INIT,
        .m_name = "custom3",
        .m_doc = "Example module that creates an extension type.",
        .m_size = -1,
    };

    PyMODINIT_FUNC
    PyInit_custom3(void)
    {
        PyObject *m;
        if (PyType_Ready(&CustomType) < 0)
            return NULL;

        m = PyModule_Create(&custommodule);
        if (m == NULL)
            return NULL;

        Py_INCREF(&CustomType);
        if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
            Py_DECREF(&CustomType);
            Py_DECREF(m);
            return NULL;
        }

        return m;
    }

Para proporcionar un mayor control sobre los atributos `first` y `last`, usaremos funciones personalizadas getter y setter. Estas son las funciones para obtener y configurar el atributo `first`:

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

    static int
    Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
    {
        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;
    }

La función getter se pasa al objeto `Custom` y un «cierre» (closure), que es un puntero nulo. En este caso, se ignora el cierre. (El cierre admite un uso avanzado en el que los datos de definición se pasan al captador y al definidor. Esto podría, por ejemplo, usarse para permitir un solo conjunto de funciones de captador y definidor que deciden que el atributo se obtenga o establezca en función de los datos en el cierre.)

La función setter pasa el objeto `Custom`, el nuevo valor y el cierre. El nuevo valor puede ser `NULL`, en cuyo caso se está eliminando el atributo. En nuestro setter, generamos un error si el atributo se elimina o si su nuevo valor no es una cadena.

Creamos un arreglo de estructuras `PyGetSetDef`:

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

y lo registra en la ranura `tp_getset`:

    .tp_getset = Custom_getsetters,

El último elemento en la estructura `PyGetSetDef` es el «cierre» (closure) mencionado anteriormente. En este caso, no estamos usando un cierre, por lo que simplemente pasamos `NULL`.

También eliminamos las definiciones de miembro para estos atributos:

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

También necesitamos actualizar el manejador `tp_init` para permitir que solo se pasen las cadenas 3:

    static int
    Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
    {
        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;
    }

Con estos cambios, podemos asegurar que los miembros `primero` y `último` nunca sean `NULL`, por lo que podemos eliminar las comprobaciones de los valores `NULL` en casi todos los casos. Esto significa que la mayoría de las llamadas `Py_XDECREF()` se pueden convertir en llamadas `Py_DECREF()`. El único lugar donde no podemos cambiar estas llamadas es en la implementación `tp_dealloc`, donde existe la posibilidad de que la inicialización de estos miembros falle en `tp_new`.

También cambiamos el nombre de la función de inicialización del módulo y el nombre del módulo en la función de inicialización, como lo hicimos antes, y agregamos una definición adicional al archivo `setup.py`.

## 2.4. Apoyo a la recolección de basura cíclica

Python tiene un recolector de basura cíclico (GC) que puede identificar objetos innecesarios incluso cuando sus recuentos de referencia no son cero. Esto puede suceder cuando los objetos están involucrados en ciclos. Por ejemplo, considere:

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

En este ejemplo, creamos una lista que se contiene a sí misma. Cuando lo eliminamos, todavía tiene una referencia de sí mismo. Su recuento de referencia no cae a cero. Afortunadamente, el recolector cíclico de basura de Python finalmente descubrirá que la lista es basura y la liberará.

En la segunda versión del ejemplo `Custom`, permitimos que cualquier tipo de objeto se almacene en `first` o `last` atributos 4. Además, en la segunda y tercera versión, permitimos subclases `Custom`, y las subclases pueden agregar atributos arbitrarios. Por cualquiera de esos dos motivos, los objetos `Custom` pueden participar en ciclos:

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

Para permitir que una instancia de `Custom` que participa en un ciclo de referencia sea detectada y recolectada correctamente por el GC cíclico, nuestro tipo `Custom` necesita llenar dos espacios adicionales y habilitar un indicador que permita estos espacios:

    #define PY_SSIZE_T_CLEAN
    #include <Python.h>
    #include "structmember.h"

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

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

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

    static void
    Custom_dealloc(CustomObject *self)
    {
        PyObject_GC_UnTrack(self);
        Custom_clear(self);
        Py_TYPE(self)->tp_free((PyObject *) 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 = 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;
    }

    static int
    Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
    {
        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;
    }

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

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

    static int
    Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
    {
        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_INCREF(value);
        Py_CLEAR(self->first);
        self->first = value;
        return 0;
    }

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

    static int
    Custom_setlast(CustomObject *self, PyObject *value, void *closure)
    {
        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_INCREF(value);
        Py_CLEAR(self->last);
        self->last = value;
        return 0;
    }

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

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

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

    static PyTypeObject CustomType = {
        PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "custom4.Custom",
        .tp_doc = "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 = (initproc) Custom_init,
        .tp_dealloc = (destructor) Custom_dealloc,
        .tp_traverse = (traverseproc) Custom_traverse,
        .tp_clear = (inquiry) Custom_clear,
        .tp_members = Custom_members,
        .tp_methods = Custom_methods,
        .tp_getset = Custom_getsetters,
    };

    static PyModuleDef custommodule = {
        PyModuleDef_HEAD_INIT,
        .m_name = "custom4",
        .m_doc = "Example module that creates an extension type.",
        .m_size = -1,
    };

    PyMODINIT_FUNC
    PyInit_custom4(void)
    {
        PyObject *m;
        if (PyType_Ready(&CustomType) < 0)
            return NULL;

        m = PyModule_Create(&custommodule);
        if (m == NULL)
            return NULL;

        Py_INCREF(&CustomType);
        if (PyModule_AddObject(m, "Custom", (PyObject *) &CustomType) < 0) {
            Py_DECREF(&CustomType);
            Py_DECREF(m);
            return NULL;
        }

        return m;
    }

Primero, el método transversal permite que el GC cíclico conozca los subobjetos que podrían participar en los ciclos:

    static int
    Custom_traverse(CustomObject *self, visitproc visit, void *arg)
    {
        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 puede participar en ciclos, necesitamos llamar a la función `visit()`, que se pasa al método transversal. La función `visit()` toma como argumentos el subobjeto y el argumento extra arg pasados al método transversal. Retorna un valor entero que debe retornarse si no es cero.

Python proporciona una macro `Py_VISIT()` que automatiza las funciones de visita de llamada. Con `Py_VISIT()`, podemos minimizar la cantidad de repeticiones en `Custom_traverse`:

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

Nota

La implementación `tp_traverse` debe nombrar sus argumentos exactamente visit y arg para usar `Py_VISIT()`.

En segundo lugar, debemos proporcionar un método para borrar cualquier subobjeto que pueda participar en los ciclos:

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

Observe el uso de la macro `Py_CLEAR()`. Es la forma recomendada y segura de borrar los atributos de datos de tipos arbitrarios al tiempo que disminuye sus recuentos de referencia. Si tuviera que llamar a `Py_XDECREF()` en lugar del atributo antes de establecerlo en `NULL`, existe la posibilidad de que el destructor del atributo vuelva a llamar al código que lee el atributo nuevamente (especialmente si hay un ciclo de referencia).

Nota

Puede emular `Py_CLEAR()` escribiendo:

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

Sin embargo, es mucho más fácil y menos propenso a errores usar siempre `Py_CLEAR()` al eliminar un atributo. ¡No intentes micro-optimizar a expensas de la robustez!

El desasignador `Custom_dealloc` puede llamar a un código arbitrario al borrar los atributos. Significa que el GC circular se puede activar dentro de la función. Dado que el GC asume que el recuento de referencias no es cero, debemos destrabar el objeto del GC llamando a `PyObject_GC_UnTrack()` antes de borrar los miembros. Aquí está nuestro reubicador reimplementado usando `PyObject_GC_UnTrack()` y `Custom_clear`:

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

Finalmente, agregamos el indicador `Py_TPFLAGS_HAVE_GC` a los indicadores de clase:

    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,

Eso es prácticamente todo. Si hubiéramos escrito controladores personalizados `tp_alloc` o `tp_free`, tendríamos que modificarlos para la recolección de basura cíclica. La mayoría de las extensiones usarán las versiones proporcionadas automáticamente.

## 2.5. Subclases de otros tipos

Es posible crear nuevos tipos de extensión que se derivan de los tipos existentes. Es más fácil heredar de los tipos incorporados, ya que una extensión puede usar fácilmente `PyTypeObject` que necesita. Puede ser difícil compartir estas estructuras `PyTypeObject` entre módulos de extensión.

En este ejemplo crearemos un tipo `SubList` que hereda del tipo incorporado `list`. El nuevo tipo será completamente compatible con las listas regulares, pero tendrá un método adicional `increment()` que aumenta un 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(SubListObject *self, PyObject *unused)
    {
        self->state++;
        return PyLong_FromLong(self->state);
    }

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

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

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

    static PyModuleDef sublistmodule = {
        PyModuleDef_HEAD_INIT,
        .m_name = "sublist",
        .m_doc = "Example module that creates an extension type.",
        .m_size = -1,
    };

    PyMODINIT_FUNC
    PyInit_sublist(void)
    {
        PyObject *m;
        SubListType.tp_base = &PyList_Type;
        if (PyType_Ready(&SubListType) < 0)
            return NULL;

        m = PyModule_Create(&sublistmodule);
        if (m == NULL)
            return NULL;

        Py_INCREF(&SubListType);
        if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
            Py_DECREF(&SubListType);
            Py_DECREF(m);
            return NULL;
        }

        return m;
    }

Como puede ver, el código fuente se parece mucho a los ejemplos `Custom` en secciones anteriores. Desglosaremos las principales diferencias entre ellos.

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

La diferencia principal para los objetos de tipo derivado es que la estructura de objeto del tipo base debe ser el primer valor. El tipo base ya incluirá `PyObject_HEAD()` al comienzo de su estructura.

Cuando un objeto Python es una instancia de `SubList`, su puntero `PyObject *` se puede convertir de forma segura tanto en `PyListObject *` como en `SubListObject *`:

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

Vemos arriba cómo llamar al método `__init__` del tipo base.

Este patrón es importante cuando se escribe un tipo con miembros personalizados `tp_new` y `tp_dealloc`. El manejador `tp_new` no debería crear realmente la memoria para el objeto con su `tp_alloc`, pero deja que la clase base lo maneje llamando a su propio `tp_new`.

La estructura `PyTypeObject` admite a `tp_base` especificando la clase base concreta del tipo. Debido a problemas de compilación multiplataforma, no puede llenar ese campo directamente con una referencia a `PyList_Type`; debe hacerse más tarde en la función de inicialización del módulo:

    PyMODINIT_FUNC
    PyInit_sublist(void)
    {
        PyObject* m;
        SubListType.tp_base = &PyList_Type;
        if (PyType_Ready(&SubListType) < 0)
            return NULL;

        m = PyModule_Create(&sublistmodule);
        if (m == NULL)
            return NULL;

        Py_INCREF(&SubListType);
        if (PyModule_AddObject(m, "SubList", (PyObject *) &SubListType) < 0) {
            Py_DECREF(&SubListType);
            Py_DECREF(m);
            return NULL;
        }

        return m;
    }

Antes de llamar a `PyType_Ready()`, la estructura de tipo debe tener el espacio `tp_base` rellenado. Cuando derivamos un tipo existente, no es necesario completar el `tp_alloc` ranura con `PyType_GenericNew()` – la función de asignación del tipo base será heredada.

Después de eso, llamar a `PyType_Ready()` y agregar el objeto tipo al módulo es lo mismo que con los ejemplos básicos `Custom`.

Notas al pie

1

  Esto es cierto cuando sabemos que el objeto es un tipo básico, como una cadena o un flotador.

2

  Nos basamos en esto en el manejador `tp_dealloc` en este ejemplo, porque nuestro tipo no admite la recolección de basura.

3

  Ahora sabemos que el primer y el último miembro son cadenas de caracteres, por lo que quizás podríamos ser menos cuidadosos al disminuir sus recuentos de referencia, sin embargo, aceptamos instancias de subclases de cadenas. A pesar de que la desasignación de cadenas normales no volverá a llamar a nuestros objetos, no podemos garantizar que la desasignación de una instancia de una subclase de cadena de caracteres no vuelva a llamar a nuestros objetos.

4

  Además, incluso con nuestros atributos restringidos a instancias de cadenas, el usuario podría pasar subclases arbitrarias `str` y, por lo tanto, seguir creando ciclos de referencia.