2. Defining Extension Types: Tutorial

Python ermöglicht es dem Autor eines C-Erweiterungsmoduls, neue Typen zu definieren, die aus Python-Code manipuliert werden können, ähnlich wie die eingebauten Typen str und list. Der Code für alle Erweiterungstypen folgt einem Muster, aber es gibt einige Details, die Sie verstehen müssen, bevor Sie loslegen können. Dieses Dokument ist eine sanfte Einführung in das Thema.

2.1. The Basics

Die CPython-Laufzeitumgebung betrachtet alle Python-Objekte als Variablen vom Typ PyObject*, der als „Basistyp“ für alle Python-Objekte dient. Die PyObject-Struktur selbst enthält nur den Referenzzähler des Objekts und einen Zeiger auf das „Typobjekt“ des Objekts. Hier passiert die eigentliche Arbeit; das Typobjekt bestimmt, welche (C-)Funktionen vom Interpreter aufgerufen werden, wenn zum Beispiel ein Attribut eines Objekts nachgeschlagen, eine Methode aufgerufen oder es mit einem anderen Objekt multipliziert wird. Diese C-Funktionen werden „Typmethoden“ genannt.

Wenn Sie also einen neuen Erweiterungstyp definieren möchten, müssen Sie ein neues Typobjekt erstellen.

Diese Art von Dingen kann nur anhand eines Beispiels erklärt werden, hier ist also ein minimales, aber vollständiges Modul, das einen neuen Typ namens Custom innerhalb eines C-Erweiterungsmoduls custom definiert

Hinweis

Was wir hier zeigen, ist der traditionelle Weg, *statische* Erweiterungstypen zu definieren. Er sollte für die meisten Anwendungsfälle ausreichend sein. Die C-API ermöglicht auch die Definition von heap-allozierten Erweiterungstypen mit der Funktion PyType_FromSpec(), die in diesem Tutorial nicht behandelt wird.

#define PY_SSIZE_T_CLEAN
#include <Python.h>

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
} 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},
    // Just use this while using static types
    {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);
}

Das ist jetzt ziemlich viel auf einmal zu verdauen, aber hoffentlich kommen Ihnen einige Teile aus dem vorherigen Kapitel bekannt vor. Diese Datei definiert drei Dinge

  1. Was ein Custom **Objekt** enthält: dies ist die CustomObject Struktur, die einmal für jede Custom-Instanz alloziert wird.

  2. Wie sich der Custom **Typ** verhält: dies ist die CustomType Struktur, die eine Reihe von Flags und Funktionszeigern definiert, die der Interpreter bei bestimmten angeforderten Operationen überprüft.

  3. Wie das custom Modul definiert und ausgeführt wird: dies ist die PyInit_custom Funktion und die zugehörige custom_module Struktur zur Definition des Moduls, sowie die custom_module_exec Funktion zur Einrichtung eines frischen Modulobjekts.

Der erste Teil ist

typedef struct {
    PyObject_HEAD
} CustomObject;

Das ist es, was ein Custom-Objekt enthalten wird. PyObject_HEAD ist zwingend am Anfang jeder Objektstruktur erforderlich und definiert ein Feld namens ob_base vom Typ PyObject, das einen Zeiger auf ein Typobjekt und einen Referenzzähler enthält (diese sind über die Makros Py_TYPE und Py_REFCNT zugänglich). Der Grund für das Makro ist, das Layout zu abstrahieren und zusätzliche Felder in Debug-Builds zu ermöglichen.

Hinweis

Über dem Makro PyObject_HEAD steht kein Semikolon. Seien Sie vorsichtig, wenn Sie versehentlich eines hinzufügen: Einige Compiler werden sich beschweren.

Natürlich speichern Objekte im Allgemeinen zusätzliche Daten neben dem standardmäßigen PyObject_HEAD-Boilerplate; hier ist zum Beispiel die Definition für Standard-Python-Floats

typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

Der zweite Teil ist die Definition des Typobjekts.

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

Hinweis

Wir empfehlen die Verwendung von C99-konformen, benannten Initialisierern wie oben, um alle PyTypeObject-Felder, die Sie nicht benötigen, nicht auflisten zu müssen und sich auch nicht um die Deklarationsreihenfolge der Felder kümmern zu müssen.

Die tatsächliche Definition von PyTypeObject in object.h hat viel mehr Felder als die obige Definition. Die verbleibenden Felder werden vom C-Compiler mit Nullen gefüllt, und es ist üblich, sie nicht explizit anzugeben, es sei denn, Sie benötigen sie.

Wir werden sie Stück für Stück auseinandernehmen

.ob_base = PyVarObject_HEAD_INIT(NULL, 0)

Diese Zeile ist obligatorisches Boilerplate, um das oben erwähnte Feld ob_base zu initialisieren.

.tp_name = "custom.Custom",

Der Name unseres Typs. Dieser wird in der Standard-Textdarstellung unserer Objekte und in einigen Fehlermeldungen angezeigt, zum Beispiel

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

Beachten Sie, dass der Name ein punktierter Name ist, der sowohl den Modulnamen als auch den Namen des Typs innerhalb des Moduls enthält. Das Modul ist in diesem Fall custom und der Typ ist Custom, also setzen wir den Typnamen auf custom.Custom. Die Verwendung des tatsächlichen punktierten Importpfads ist wichtig, um Ihren Typ mit den Modulen pydoc und pickle kompatibel zu machen.

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

Dies dient dazu, dass Python weiß, wie viel Speicher beim Erstellen neuer Custom-Instanzen alloziert werden muss. tp_itemsize wird nur für variable Objekte verwendet und sollte ansonsten Null sein.

Hinweis

Wenn Sie möchten, dass Ihr Typ von Python aus unterklassbar ist und Ihr Typ die gleiche tp_basicsize wie sein Basistyp hat, können Sie Probleme mit Mehrfachvererbung haben. Eine Python-Unterklasse Ihres Typs muss Ihren Typ zuerst in ihrer __bases__ auflisten, sonst kann sie die __new__()-Methode Ihres Typs nicht aufrufen, ohne eine Fehlermeldung zu erhalten. Sie können dieses Problem vermeiden, indem Sie sicherstellen, dass Ihr Typ einen höheren Wert für tp_basicsize hat als sein Basistyp. Meistens ist dies sowieso der Fall, da entweder Ihr Basistyp object ist oder Sie Datenmember zu Ihrem Basistyp hinzufügen und damit dessen Größe erhöhen.

Wir setzen die Klassen-Flags auf Py_TPFLAGS_DEFAULT.

.tp_flags = Py_TPFLAGS_DEFAULT,

Alle Typen sollten diese Konstante in ihren Flags enthalten. Sie aktiviert alle Mitglieder, die bis mindestens Python 3.3 definiert wurden. Wenn Sie weitere Mitglieder benötigen, müssen Sie die entsprechenden Flags mit OR verknüpfen.

Wir stellen einen Docstring für den Typ in tp_doc bereit.

.tp_doc = PyDoc_STR("Custom objects"),

Um die Objekterstellung zu ermöglichen, müssen wir einen tp_new-Handler bereitstellen. Dies ist das Äquivalent zur Python-Methode __new__(), muss aber explizit angegeben werden. In diesem Fall können wir einfach die Standardimplementierung verwenden, die von der API-Funktion PyType_GenericNew() bereitgestellt wird.

.tp_new = PyType_GenericNew,

Alles andere in der Datei sollte vertraut sein, mit Ausnahme einiger Codezeilen in custom_module_exec()

if (PyType_Ready(&CustomType) < 0) {
    return -1;
}

Dies initialisiert den Custom-Typ und füllt eine Reihe von Feldern mit den entsprechenden Standardwerten, einschließlich ob_type, das wir anfangs auf NULL gesetzt hatten.

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

Dies fügt den Typ zum Modulverzeichnis hinzu. Dies ermöglicht uns, Custom-Instanzen zu erstellen, indem wir die Custom-Klasse aufrufen

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

Das war's! Alles, was noch bleibt, ist das Bauen; legen Sie den obigen Code in eine Datei namens custom.c,

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

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

in eine Datei namens pyproject.toml und

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

in eine Datei namens setup.py; dann tippen Sie

$ python -m pip install .

in einer Shell sollte eine Datei custom.so in einem Unterverzeichnis erzeugen und installieren; starten Sie jetzt Python — Sie sollten import custom aufrufen und mit Custom-Objekten herumspielen können.

Das war doch gar nicht so schwer, oder?

Natürlich ist der aktuelle Custom-Typ ziemlich uninteressant. Er hat keine Daten und tut nichts. Er kann nicht einmal unterklassifiziert werden.

2.2. Adding data and methods to the Basic example

Erweitern wir das Basisbeispiel, um einige Daten und Methoden hinzuzufügen. Machen wir den Typ auch als Basisklasse verwendbar. Wir erstellen ein neues Modul, custom2, das diese Fähigkeiten hinzufügt

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

Diese Version des Moduls hat eine Reihe von Änderungen.

Der Custom-Typ hat nun drei Datenattribute in seiner C-Struktur: *first*, *last* und *number*. Die Variablen *first* und *last* sind Python-Strings, die Vor- und Nachnamen enthalten. Das Attribut *number* ist ein C-Integer.

Die Objektstruktur wird entsprechend aktualisiert

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

Da wir nun Daten verwalten müssen, müssen wir bei der Speicherallokation und -freigabe vorsichtiger sein. Mindestens benötigen wir eine Deallokationsmethode

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

die dem tp_dealloc-Mitglied zugewiesen wird

.tp_dealloc = Custom_dealloc,

Diese Methode löscht zuerst die Referenzzähler der beiden Python-Attribute. Py_XDECREF() behandelt den Fall, dass sein Argument NULL ist (was hier passieren könnte, wenn tp_new auf halbem Weg fehlschlug) korrekt. Anschließend ruft sie das tp_free-Mitglied des Objekttyps (berechnet durch Py_TYPE(self)) auf, um den Speicher des Objekts freizugeben. Beachten Sie, dass der Objekttyp möglicherweise nicht CustomType ist, da das Objekt eine Instanz einer Unterklasse sein könnte.

Hinweis

Der explizite Cast zu CustomObject * ist oben erforderlich, da wir Custom_dealloc so definiert haben, dass sie ein Argument vom Typ PyObject * entgegennimmt, da der Funktionszeiger tp_dealloc ein Argument vom Typ PyObject * erwartet. Indem wir dem tp_dealloc-Slot eines Typs zuweisen, deklarieren wir, dass er nur mit Instanzen unserer CustomObject-Klasse aufgerufen werden kann, sodass der Cast zu (CustomObject *) sicher ist. Das ist objektorientierte Polymorphie, in C!

In bestehendem Code oder in früheren Versionen dieses Tutorials sehen Sie möglicherweise ähnliche Funktionen, die direkt einen Zeiger auf die Untertyp-Objektstruktur (CustomObject*) entgegennehmen, wie hier:

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

Dies tut dasselbe auf allen Architekturen, die CPython unterstützt, aber laut C-Standard ruft es undefiniertes Verhalten auf.

Wir möchten sicherstellen, dass die Vornamen und Nachnamen auf leere Strings initialisiert werden, daher stellen wir eine tp_new-Implementierung bereit

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

und installieren sie im tp_new-Slot

.tp_new = Custom_new,

Der tp_new-Handler ist für die Erstellung (nicht für die Initialisierung) von Objekten des Typs zuständig. Er wird in Python als Methode __new__() verfügbar gemacht. Es ist nicht erforderlich, ein tp_new-Mitglied zu definieren, und tatsächlich werden viele Erweiterungstypen einfach PyType_GenericNew() wiederverwenden, wie es in der ersten Version des Custom-Typs oben gezeigt wurde. In diesem Fall verwenden wir den tp_new-Handler, um die Attribute first und last auf nicht-NULL-Standardwerte zu initialisieren.

tp_new wird der zu instanziierende Typ (nicht notwendigerweise CustomType, wenn eine Unterklasse instanziiert wird) und alle Argumente, die beim Aufruf des Typs übergeben wurden, übergeben, und es wird erwartet, dass es die erstellte Instanz zurückgibt. tp_new-Handler akzeptieren immer positions- und schlüsselwortargumente, ignorieren aber oft die Argumente und überlassen die Argumentenbehandlung den Initialisierungsmethoden (auch bekannt als tp_init in C oder __init__ in Python).

Hinweis

tp_new sollte tp_init nicht explizit aufrufen, da der Interpreter dies selbst tut.

Die tp_new-Implementierung ruft den tp_alloc-Slot auf, um Speicher zu alloziieren

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

Da die Speicherallokation fehlschlagen kann, müssen wir das Ergebnis von tp_alloc gegen NULL prüfen, bevor wir fortfahren.

Hinweis

Wir haben den tp_alloc-Slot nicht selbst gefüllt. Stattdessen füllt PyType_Ready() ihn für uns, indem er ihn von unserer Basisklasse erbt, die standardmäßig object ist. Die meisten Typen verwenden die Standard-Allokationsstrategie.

Hinweis

Wenn Sie einen kooperativen tp_new erstellen (einen, der die tp_new oder __new__() einer Basisklasse aufruft), dürfen Sie *nicht* versuchen, zur Laufzeit mithilfe der Method Resolution Order zu bestimmen, welche Methode aufgerufen werden soll. Bestimmen Sie immer statisch, welchen Typ Sie aufrufen werden, und rufen Sie dessen tp_new direkt oder über type->tp_base->tp_new auf. Wenn Sie dies nicht tun, funktionieren Python-Unterklassen Ihres Typs, die auch von anderen Python-definierten Klassen erben, möglicherweise nicht korrekt. (Insbesondere können Sie Instanzen solcher Unterklassen möglicherweise nicht erstellen, ohne einen TypeError zu erhalten.)

Wir definieren außerdem eine Initialisierungsfunktion, die Argumente entgegennimmt, um Anfangswerte für unsere Instanz bereitzustellen

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, "|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;
}

durch Ausfüllen des tp_init-Slots.

.tp_init = Custom_init,

Der tp_init-Slot wird in Python als Methode __init__() verfügbar gemacht. Sie dient zur Initialisierung eines Objekts nach dessen Erstellung. Initialisierer akzeptieren immer positions- und schlüsselwortargumente und sollten bei Erfolg 0 oder bei einem Fehler -1 zurückgeben.

Im Gegensatz zum tp_new-Handler gibt es keine Garantie, dass tp_init überhaupt aufgerufen wird (zum Beispiel ruft das Modul pickle standardmäßig __init__() bei entpackten Instanzen nicht auf). Es kann auch mehrmals aufgerufen werden. Jeder kann die Methode __init__() für unsere Objekte aufrufen. Aus diesem Grund müssen wir beim Zuweisen neuer Attributwerte besonders vorsichtig sein. Wir könnten versucht sein, zum Beispiel das Feld first wie folgt zuzuweisen

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

Aber das wäre riskant. Unser Typ beschränkt den Typ des Feldes first nicht, daher könnte es sich um jede Art von Objekt handeln. Es könnte einen Destruktor haben, der Code ausführt, der versucht, auf das Feld first zuzugreifen; oder dieser Destruktor könnte den Thread-Status lösen und beliebigen Code in anderen Threads ausführen lassen, der unser Objekt aufruft und modifiziert.

Um paranoid zu sein und uns vor dieser Möglichkeit zu schützen, überschreiben wir Mitglieder fast immer, bevor wir ihre Referenzzähler dekrementieren. Wann müssen wir das nicht tun?

  • wenn wir absolut sicher sind, dass der Referenzzähler größer als 1 ist;

  • wenn wir wissen, dass die Deallokation des Objekts [1] weder den Thread-Status löst noch irgendwelche Rückrufe in den Code unseres Typs verursacht;

  • beim Dekrementieren eines Referenzzählers in einem tp_dealloc-Handler eines Typs, der keine zyklische Speicherbereinigung unterstützt [2].

Wir möchten unsere Instanzvariablen als Attribute verfügbar machen. Es gibt eine Reihe von Möglichkeiten, dies zu tun. Der einfachste Weg ist die Definition von Mitgliederdefinitionen

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 */
};

und die Platzierung der Definitionen im tp_members-Slot

.tp_members = Custom_members,

Jede Mitgliederdefinition hat einen Mitgliedsnamen, einen Typ, einen Offset, Zugriffsflags und eine Dokumentationszeichenkette. Details finden Sie im Abschnitt Generic Attribute Management unten.

Ein Nachteil dieses Ansatzes ist, dass er keine Möglichkeit bietet, die Typen von Objekten einzuschränken, die den Python-Attributen zugewiesen werden können. Wir erwarten, dass die Vor- und Nachnamen Strings sind, aber beliebige Python-Objekte können zugewiesen werden. Außerdem können die Attribute gelöscht werden, wodurch die C-Zeiger auf NULL gesetzt werden. Selbst wenn wir sicherstellen können, dass die Mitglieder mit nicht-NULL-Werten initialisiert werden, können die Mitglieder auf NULL gesetzt werden, wenn die Attribute gelöscht werden.

Wir definieren eine einzelne Methode, Custom.name(), die den Namen des Objekts als Verkettung des Vor- und Nachnamens ausgibt.

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

Die Methode wird als C-Funktion implementiert, die eine Instanz von Custom (oder einer Custom-Unterklasse) als erstes Argument erhält. Methoden erhalten immer eine Instanz als erstes Argument. Methoden erhalten oft auch positions- und schlüsselwortargumente, aber in diesem Fall nehmen wir keine und müssen kein Tupel für positionsargumente oder ein Wörterbuch für schlüsselwortargumente akzeptieren. Diese Methode entspricht der Python-Methode

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

Beachten Sie, dass wir auf die Möglichkeit prüfen müssen, dass unsere Mitglieder first und last NULL sind. Das liegt daran, dass sie gelöscht werden können, in welchem Fall sie auf NULL gesetzt werden. Es wäre besser, das Löschen dieser Attribute zu verhindern und die Attributwerte auf Strings zu beschränken. Wie wir das tun, sehen wir im nächsten Abschnitt.

Nachdem wir nun die Methode definiert haben, müssen wir ein Array von Methodendefinitionen erstellen

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

(beachten Sie, dass wir das Flag METH_NOARGS verwendet haben, um anzuzeigen, dass die Methode außer *self* keine Argumente erwartet)

und es dem tp_methods-Slot zuweisen

.tp_methods = Custom_methods,

Schließlich machen wir unseren Typ als Basisklasse für Unterklassen nutzbar. Wir haben unsere Methoden bisher sorgfältig geschrieben, so dass sie keine Annahmen über den Typ des erstellten oder verwendeten Objekts treffen, daher müssen wir nur Py_TPFLAGS_BASETYPE zu unserer Klassendefinition hinzufügen

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,

Wir benennen PyInit_custom() in PyInit_custom2() um, aktualisieren den Modulnamen in der PyModuleDef-Struktur und aktualisieren den vollständigen Klassennamen in der PyTypeObject-Struktur.

Schließlich aktualisieren wir unsere Datei setup.py, um das neue Modul aufzunehmen,

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

und installieren es dann neu, damit wir import custom2 können

$ python -m pip install .

2.3. Providing finer control over data attributes

In diesem Abschnitt werden wir die Kontrolle darüber verfeinern, wie die Attribute first und last im Custom-Beispiel gesetzt werden. In der vorherigen Version unseres Moduls konnten die Instanzvariablen first und last auf Nicht-String-Werte gesetzt oder sogar gelöscht werden. Wir möchten sicherstellen, dass diese Attribute immer Strings enthalten.

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

Um mehr Kontrolle über die Attribute first und last zu erhalten, verwenden wir benutzerdefinierte Getter- und Setter-Funktionen. Hier sind die Funktionen zum Abrufen und Setzen des Attributs 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;
}

Die Getter-Funktion erhält ein Custom-Objekt und eine „Closure“, einen void-Zeiger. In diesem Fall wird die Closure ignoriert. (Die Closure unterstützt eine fortgeschrittene Verwendung, bei der Definitionsdaten an den Getter und Setter übergeben werden. Dies könnte zum Beispiel verwendet werden, um einen einzigen Satz von Getter- und Setter-Funktionen zu ermöglichen, die das abzurufende oder zu setzende Attribut basierend auf Daten in der Closure entscheiden.)

Die Setter-Funktion erhält das Custom-Objekt, den neuen Wert und die Closure. Der neue Wert kann NULL sein, in welchem Fall das Attribut gelöscht wird. In unserem Setter lösen wir einen Fehler aus, wenn das Attribut gelöscht wird oder sein neuer Wert kein String ist.

Wir erstellen ein Array von PyGetSetDef-Strukturen

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

und registrieren es im tp_getset-Slot

.tp_getset = Custom_getsetters,

Das letzte Element in einer PyGetSetDef-Struktur ist die oben erwähnte „Closure“. In diesem Fall verwenden wir keine Closure, daher übergeben wir einfach NULL.

Wir entfernen auch die Mitgliederdefinitionen für diese Attribute

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

Wir müssen auch den tp_init-Handler aktualisieren, um nur Strings [3] zuzulassen

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

Mit diesen Änderungen können wir sicherstellen, dass die Member first und last niemals NULL sind, sodass wir in fast allen Fällen auf Prüfungen auf NULL-Werte verzichten können. Dies bedeutet, dass die meisten Py_XDECREF()-Aufrufe in Aufrufe von Py_DECREF() umgewandelt werden können. Die einzige Stelle, an der wir diese Aufrufe nicht ändern können, ist in der Implementierung von tp_dealloc, da es möglich ist, dass die Initialisierung dieser Member in tp_new fehlgeschlagen ist.

Wir benennen auch die Modulinitialisierungsfunktion und den Modulnamen in der Initialisierungsfunktion um, wie wir es zuvor getan haben, und fügen eine zusätzliche Definition zur Datei setup.py hinzu.

2.4. Unterstützung der zyklischen Garbage Collection

Python verfügt über einen zyklischen Garbage Collector (GC), der nicht mehr benötigte Objekte identifizieren kann, auch wenn ihre Referenzzähler nicht Null sind. Dies kann passieren, wenn Objekte an Zyklen beteiligt sind. Betrachten Sie beispielsweise

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

In diesem Beispiel erstellen wir eine Liste, die sich selbst enthält. Wenn wir sie löschen, hat sie immer noch eine Referenz von sich selbst. Ihr Referenzzähler fällt nicht auf Null. Glücklicherweise wird der zyklische Garbage Collector von Python schließlich feststellen, dass die Liste Müll ist und sie freigeben.

In der zweiten Version des Custom-Beispiels erlaubten wir, dass beliebige Objekte in den Attributen first oder last gespeichert werden [4]. Darüber hinaus erlaubten wir in der zweiten und dritten Version die Unterklassifizierung von Custom, und Unterklassen können beliebige Attribute hinzufügen. Aus einem dieser beiden Gründe können Custom-Objekte an Zyklen beteiligt sein.

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

Damit eine Custom-Instanz, die an einem Referenzzyklus beteiligt ist, ordnungsgemäß vom zyklischen GC erkannt und gesammelt werden kann, muss unser Custom-Typ zwei zusätzliche Slots füllen und ein Flag setzen, das diese Slots aktiviert.

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

Erstens informiert die Traversal-Methode den zyklischen GC über Unterobjekte, die an Zyklen beteiligt sein könnten.

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

Für jedes Unterobjekt, das an Zyklen beteiligt sein kann, müssen wir die Funktion visit() aufrufen, die an die Traversal-Methode übergeben wird. Die Funktion visit() nimmt als Argumente das Unterobjekt und das zusätzliche Argument arg entgegen, das an die Traversal-Methode übergeben wird. Sie gibt einen ganzzahligen Wert zurück, der zurückgegeben werden muss, wenn er ungleich Null ist.

Python stellt ein Py_VISIT()-Makro zur Verfügung, das den Aufruf von Visit-Funktionen automatisiert. Mit Py_VISIT() können wir den Umfang des Boilerplate-Codes in Custom_traverse minimieren.

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

Hinweis

Die Implementierung von tp_traverse muss ihre Argumente exakt visit und arg nennen, um Py_VISIT() verwenden zu können.

Zweitens müssen wir eine Methode zum Löschen von Unterobjekten bereitstellen, die an Zyklen beteiligt sein können.

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

Beachten Sie die Verwendung des Makros Py_CLEAR(). Es ist der empfohlene und sichere Weg, Datenattribute von beliebigen Typen zu löschen, während ihre Referenzzähler dekrementiert werden. Wenn Sie stattdessen Py_XDECREF() auf das Attribut anwenden würden, bevor Sie es auf NULL setzen, besteht die Möglichkeit, dass der Destruktor des Attributs zurück in Code aufruft, der das Attribut erneut liest (insbesondere bei Referenzzyklen).

Hinweis

Sie könnten Py_CLEAR() emulieren, indem Sie schreiben:

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

Dennoch ist es viel einfacher und weniger fehleranfällig, beim Löschen eines Attributs immer Py_CLEAR() zu verwenden. Versuchen Sie nicht, Mikrooptimierungen auf Kosten der Robustheit vorzunehmen!

Der Deallokator Custom_dealloc kann beim Löschen von Attributen beliebigen Code aufrufen. Das bedeutet, dass der zyklische GC innerhalb der Funktion ausgelöst werden kann. Da der GC davon ausgeht, dass der Referenzzähler nicht Null ist, müssen wir das Objekt vom GC abmelden, indem wir PyObject_GC_UnTrack() aufrufen, bevor wir Member löschen. Hier ist unser neu implementierter Deallokator, der PyObject_GC_UnTrack() und Custom_clear verwendet.

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

Schließlich fügen wir das Flag Py_TPFLAGS_HAVE_GC zu den Klassen-Flags hinzu.

.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,

Das ist im Grunde alles. Wenn wir benutzerdefinierte tp_alloc- oder tp_free-Handler geschrieben hätten, müssten wir diese für die zyklische Garbage Collection anpassen. Die meisten Erweiterungen werden die automatisch bereitgestellten Versionen verwenden.

2.5. Unterklassifizierung anderer Typen

Es ist möglich, neue Erweiterungstypen zu erstellen, die von vorhandenen Typen abgeleitet sind. Am einfachsten ist es, von den integrierten Typen zu erben, da eine Erweiterung leicht die benötigte PyTypeObject verwenden kann. Es kann schwierig sein, diese PyTypeObject-Strukturen zwischen Erweiterungsmodulen zu teilen.

In diesem Beispiel erstellen wir einen SubList-Typ, der vom integrierten list-Typ erbt. Der neue Typ wird vollständig mit regulären Listen kompatibel sein, aber eine zusätzliche Methode increment() haben, die einen internen Zähler erhöht.

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

Wie Sie sehen können, ähnelt der Quellcode stark den Custom-Beispielen in den vorherigen Abschnitten. Wir werden die wichtigsten Unterschiede zwischen ihnen aufschlüsseln.

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

Der Hauptunterschied bei abgeleiteten Typobjekten besteht darin, dass die Objektstruktur des Basistyps der erste Wert sein muss. Der Basistyp enthält bereits PyObject_HEAD() am Anfang seiner Struktur.

Wenn ein Python-Objekt eine SubList-Instanz ist, kann sein PyObject *-Zeiger sicher sowohl in PyListObject * als auch in SubListObject * umgewandelt werden.

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

Wir sehen oben, wie man die Methode __init__() des Basistyps aufruft.

Dieses Muster ist wichtig beim Schreiben eines Typs mit benutzerdefinierten tp_new- und tp_dealloc-Membern. Der tp_new-Handler sollte nicht tatsächlich den Speicher für das Objekt mit seinem tp_alloc erstellen, sondern die Basisklasse dies erledigen lassen, indem sie ihren eigenen tp_new aufruft.

Die Struktur PyTypeObject unterstützt ein Feld tp_base, das die konkrete Basisklasse des Typs angibt. Aufgrund von plattformübergreifenden Compilerproblemen können Sie dieses Feld nicht direkt mit einer Referenz auf PyList_Type füllen; dies sollte in der Funktion Py_mod_exec geschehen.

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

Bevor PyType_Ready() aufgerufen wird, muss der Typtyp die Slot tp_base gefüllt haben. Wenn wir einen vorhandenen Typ ableiten, ist es nicht notwendig, den Slot tp_alloc mit PyType_GenericNew() zu füllen – die Allokationsfunktion des Basistyps wird geerbt.

Danach ist das Aufrufen von PyType_Ready() und das Hinzufügen des Typobjekts zum Modul dasselbe wie bei den grundlegenden Custom-Beispielen.

Fußnoten