# 3. Définir les types d'extension : divers sujets

Cette section vise à donner un aperçu rapide des différentes méthodes de type que vous pouvez implémenter et de ce qu'elles font.

Voici la définition de `PyTypeObject`, après avoir enlevé certains champs utilisés uniquement dans debug builds :

    typedef struct _typeobject {
        PyObject_VAR_HEAD
        const char *tp_name; /* For printing, in format "<module>.<name>" */
        Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

        /* Methods to implement standard operations */

        destructor tp_dealloc;
        Py_ssize_t tp_vectorcall_offset;
        getattrfunc tp_getattr;
        setattrfunc tp_setattr;
        PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
                                        or tp_reserved (Python 3) */
        reprfunc tp_repr;

        /* Method suites for standard classes */

        PyNumberMethods *tp_as_number;
        PySequenceMethods *tp_as_sequence;
        PyMappingMethods *tp_as_mapping;

        /* More standard operations (here for binary compatibility) */

        hashfunc tp_hash;
        ternaryfunc tp_call;
        reprfunc tp_str;
        getattrofunc tp_getattro;
        setattrofunc tp_setattro;

        /* Functions to access object as input/output buffer */
        PyBufferProcs *tp_as_buffer;

        /* Flags to define presence of optional/expanded features */
        unsigned long tp_flags;

        const char *tp_doc; /* Documentation string */

        /* Assigned meaning in release 2.0 */
        /* call function for all accessible objects */
        traverseproc tp_traverse;

        /* delete references to contained objects */
        inquiry tp_clear;

        /* Assigned meaning in release 2.1 */
        /* rich comparisons */
        richcmpfunc tp_richcompare;

        /* weak reference enabler */
        Py_ssize_t tp_weaklistoffset;

        /* Iterators */
        getiterfunc tp_iter;
        iternextfunc tp_iternext;

        /* Attribute descriptor and subclassing stuff */
        struct PyMethodDef *tp_methods;
        struct PyMemberDef *tp_members;
        struct PyGetSetDef *tp_getset;
        // Strong reference on a heap type, borrowed reference on a static type
        struct _typeobject *tp_base;
        PyObject *tp_dict;
        descrgetfunc tp_descr_get;
        descrsetfunc tp_descr_set;
        Py_ssize_t tp_dictoffset;
        initproc tp_init;
        allocfunc tp_alloc;
        newfunc tp_new;
        freefunc tp_free; /* Low-level free-memory routine */
        inquiry tp_is_gc; /* For PyObject_IS_GC */
        PyObject *tp_bases;
        PyObject *tp_mro; /* method resolution order */
        PyObject *tp_cache;
        PyObject *tp_subclasses;
        PyObject *tp_weaklist;
        destructor tp_del;

        /* Type attribute cache version tag. Added in version 2.6 */
        unsigned int tp_version_tag;

        destructor tp_finalize;
        vectorcallfunc tp_vectorcall;

        /* bitset of which type-watchers care about this type */
        unsigned char tp_watched;
    } PyTypeObject;

Cela fait beaucoup de méthodes. Ne vous inquiétez pas trop cependant : si vous souhaitez définir un type, il y a de fortes chances que vous n'implémentiez qu'une petite partie d'entre elles.

Comme vous vous en doutez probablement maintenant, nous allons passer en revue cela et donner plus d'informations sur les différents gestionnaires. Nous ne suivrons pas l'ordre dans lequel ils sont définis dans la structure, car l'ordre des champs résulte d'un certain historique. Il est souvent plus facile de trouver un exemple qui inclut les champs dont vous avez besoin, puis de modifier les valeurs en fonction de votre nouveau type :

    const char *tp_name; /* For printing */

Le nom du type – comme mentionné dans le chapitre précédent, cela apparaîtra à divers endroits, presque entièrement à des fins de diagnostic. Essayez de choisir quelque chose qui sera utile dans une telle situation !

    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

Ces champs indiquent la quantité de mémoire à allouer à l'exécution lorsque de nouveaux objets de ce type sont créés. Python prend en charge nativement des structures de longueur variable (pensez : chaînes, n-uplets), c'est là que le champ `tp_itemsize` entre en jeu. Cela sera traité plus tard.

    const char *tp_doc;

Ici vous pouvez mettre une chaîne (ou son adresse) que vous voulez renvoyer lorsque le script Python référence `obj.__doc__` pour récupérer le docstring.

Nous en arrivons maintenant aux méthodes de type basiques -- celles que la plupart des types d'extension mettront en œuvre.

## 3.1. Finalisation et libération de mémoire

    destructor tp_dealloc;

Cette fonction est appelée lorsque le compteur de références de l'instance de votre type tombe à zéro et que l'interpréteur Python veut récupérer la mémoire afférente. Si votre type a de la mémoire à libérer ou un autre nettoyage à effectuer, vous pouvez le mettre ici. L'objet lui-même doit être libéré ici aussi. Voici un exemple de cette fonction :

    static void
    newdatatype_dealloc(newdatatypeobject *obj)
    {
        free(obj->obj_UnderlyingDatatypePtr);
        Py_TYPE(obj)->tp_free((PyObject *)obj);
    }

Si votre type prend en charge le ramasse-miettes, le destructeur doit appeler `PyObject_GC_UnTrack()` avant d'effacer les champs membres :

    static void
    newdatatype_dealloc(newdatatypeobject *obj)
    {
        PyObject_GC_UnTrack(obj);
        Py_CLEAR(obj->other_obj);
        ...
        Py_TYPE(obj)->tp_free((PyObject *)obj);
    }

Une exigence importante de la fonction de libération de la mémoire est de ne pas s'occuper de toutes les exceptions en attente. C'est important car les fonctions de libération de la mémoire sont fréquemment appelées lorsque l'interpréteur remonte la pile d'appels Python ; lorsque la pile est remontée à cause d'une exception (plutôt que de retours normaux), les fonctions de libération peuvent voir qu'une exception a déjà été définie. Toute action effectuée par une fonction de libération de la mémoire pouvant entraîner l'exécution de code Python supplémentaire peut détecter qu'une exception a été définie. Cela peut conduire l’interpréteur à se tromper sur la nature de l'erreur. La bonne façon d'éviter cela est d'enregistrer une exception en attente avant d'effectuer l'action non sécurisée et à la restaurer une fois terminée. Cela peut être fait en utilisant les fonctions `PyErr_Fetch()` et `PyErr_Restore()` :

    static void
    my_dealloc(PyObject *obj)
    {
        MyObject *self = (MyObject *) obj;
        PyObject *cbresult;

        if (self->my_callback != NULL) {
            PyObject *err_type, *err_value, *err_traceback;

            /* This saves the current exception state */
            PyErr_Fetch(&err_type, &err_value, &err_traceback);

            cbresult = PyObject_CallNoArgs(self->my_callback);
            if (cbresult == NULL)
                PyErr_WriteUnraisable(self->my_callback);
            else
                Py_DECREF(cbresult);

            /* This restores the saved exception state */
            PyErr_Restore(err_type, err_value, err_traceback);

            Py_DECREF(self->my_callback);
        }
        Py_TYPE(obj)->tp_free((PyObject*)self);
    }

Note

des limites existent à ce que vous pouvez faire en toute sécurité dans une fonction de libération de la mémoire. Tout d'abord, si votre type prend en charge le ramasse-miettes (en utilisant `tp_traverse` et/ou `tp_clear`), certains membres de l'objet peuvent avoir été effacés ou finalisés avant que `tp_dealloc` ne soit appelé. Deuxièmement, dans `tp_dealloc`, votre objet est dans un état instable : son compteur de références est égal à zéro. Tout appel à un objet non trivial ou à une API (comme dans l'exemple ci-dessus) peut finir par appeler `tp_dealloc` à nouveau, provoquant une double libération et un plantage.

À partir de Python 3.4, il est recommandé de ne pas mettre de code de finalisation complexe dans `tp_dealloc`, et d'utiliser à la place la nouvelle méthode de type `tp_finalize`.

Voir aussi

PEP 442 explique le nouveau schéma de finalisation.

## 3.2. Présentation de l'objet

En Python, il existe deux façons de générer une représentation textuelle d'un objet : la fonction `repr()` et la fonction `str()` (la fonction `print()` appelle simplement `str()`). Ces gestionnaires sont tous deux facultatifs.

    reprfunc tp_repr;
    reprfunc tp_str;

Le gestionnaire `tp_repr` doit renvoyer un objet chaîne contenant une représentation de l'instance pour laquelle il est appelé. Voici un exemple simple :

    static PyObject *
    newdatatype_repr(newdatatypeobject *obj)
    {
        return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:%d}}",
                                    obj->obj_UnderlyingDatatypePtr->size);
    }

Si aucun gestionnaire `tp_repr` n'est spécifié, l'interpréteur fournira une représentation qui utilise le type `tp_name` et une valeur d'identification unique pour l'objet.

Le gestionnaire `tp_str` est à `str()` ce que le gestionnaire `tp_repr` décrit ci-dessus est à `repr()` ; c'est-à-dire qu'il est appelé lorsque le code Python appelle `str()` sur une instance de votre objet. Son implémentation est très similaire à la fonction `tp_repr`, mais la chaîne résultante est destinée à être lue par des utilisateurs. Si `tp_str` n'est pas spécifié, le gestionnaire `tp_repr` est utilisé à la place.

Voici un exemple simple :

    static PyObject *
    newdatatype_str(newdatatypeobject *obj)
    {
        return PyUnicode_FromFormat("Stringified_newdatatype{{size:%d}}",
                                    obj->obj_UnderlyingDatatypePtr->size);
    }

## 3.3. Gestion des attributs

Pour chaque objet pouvant prendre en charge des attributs, le type correspondant doit fournir les fonctions qui contrôlent la façon dont les attributs sont résolus. Il doit y avoir une fonction qui peut récupérer les attributs (le cas échéant) et une autre pour définir les attributs (si la définition des attributs est autorisée). La suppression d'un attribut est un cas particulier, pour lequel la nouvelle valeur transmise au gestionnaire est `NULL`.

Python prend en charge deux paires de gestionnaires d'attributs ; un type qui prend en charge les attributs n'a besoin d'implémenter les fonctions que pour une paire. La différence est qu'une paire prend le nom de l'attribut en tant que char*, tandis que l'autre accepte un PyObject*. Chaque type peut utiliser la paire la plus logique pour la commodité de l'implémentation.

    getattrfunc  tp_getattr;        /* char * version */
    setattrfunc  tp_setattr;
    /* ... */
    getattrofunc tp_getattro;       /* PyObject * version */
    setattrofunc tp_setattro;

Si accéder aux attributs d'un objet est toujours une opération simple (ceci sera expliqué brièvement), il existe des implémentations génériques qui peuvent être utilisées pour fournir la version PyObject* des fonctions de gestion des attributs. Le besoin réel de gestionnaires d'attributs spécifiques au type a presque complètement disparu à partir de Python 2.2, bien qu'il existe de nombreux exemples qui n'ont pas été mis à jour pour utiliser certains des nouveaux mécanismes génériques disponibles.

### 3.3.1. Gestion des attributs génériques

La plupart des types d'extensions n'utilisent que des attributs simples. Alors, qu'est-ce qui rend les attributs simples ? Seules quelques conditions doivent être remplies :

1. le nom des attributs doit être déjà connu lorsqu'on lance `PyType_Ready()` ;

2. aucun traitement spécial n'est nécessaire pour enregistrer qu'un attribut a été recherché ou défini, et aucune action ne doit être entreprise en fonction de la valeur.

Notez que cette liste n'impose aucune restriction sur les valeurs des attributs, le moment où les valeurs sont calculées ou la manière dont les données pertinentes sont stockées.

Lorsque `PyType_Ready()` est appelé, il utilise trois tableaux référencés par l'objet type pour créer des descripteurs qui sont placés dans le dictionnaire de l'objet type. Chaque descripteur contrôle l'accès à un attribut de l'objet instance. Chacun des tableaux est facultatif ; si les trois sont `NULL`, les instances du type n'auront que des attributs hérités de leur type de base et doivent laisser les champs `tp_getattro` et `tp_setattro` à `NULL` également, permettant au type de base de gérer les attributs.

Les tableaux sont déclarés sous la forme de trois champs de type objet :

    struct PyMethodDef *tp_methods;
    struct PyMemberDef *tp_members;
    struct PyGetSetDef *tp_getset;

Si `tp_methods` n'est pas `NULL`, il doit faire référence à un tableau de structures `PyMethodDef`. Chaque entrée du tableau est une instance de cette structure :

    typedef struct PyMethodDef {
        const char  *ml_name;       /* method name */
        PyCFunction  ml_meth;       /* implementation function */
        int          ml_flags;      /* flags */
        const char  *ml_doc;        /* docstring */
    } PyMethodDef;

One entry should be defined for each method provided by the type; no entries are needed for methods inherited from a base type. One additional entry is needed at the end; it is a sentinel that marks the end of the array. The `ml_name` field of the sentinel must be `NULL`.

Le deuxième tableau est utilisé pour définir les attributs qui correspondent directement aux données stockées dans l'instance. Divers types C natifs sont pris en charge et l'accès peut être en lecture seule ou en lecture-écriture. Les structures du tableau sont définies comme suit :

    typedef struct PyMemberDef {
        const char *name;
        int         type;
        int         offset;
        int         flags;
        const char *doc;
    } PyMemberDef;

For each entry in the table, a descriptor will be constructed and added to the type which will be able to extract a value from the instance structure. The `type` field should contain a type code like `Py_T_INT` or `Py_T_DOUBLE`; the value will be used to determine how to convert Python values to and from C values. The `flags` field is used to store flags which control how the attribute can be accessed: you can set it to `Py_READONLY` to prevent Python code from setting it.

An interesting advantage of using the `tp_members` table to build descriptors that are used at runtime is that any attribute defined this way can have an associated doc string simply by providing the text in the table. An application can use the introspection API to retrieve the descriptor from the class object, and get the doc string using its `__doc__` attribute.

As with the `tp_methods` table, a sentinel entry with a `ml_name` value of `NULL` is required.

### 3.3.2. Gestion des attributs de type spécifiques

Pour plus de simplicité, seule la version char* est montrée ici ; le type du paramètre name est la seule différence entre les variations char* et PyObject* de l'interface. Cet exemple fait effectivement la même chose que l'exemple générique ci-dessus, mais n'utilise pas le support générique ajouté dans Python 2.2. Il explique comment les fonctions de gestionnaire sont appelées, de sorte que si vous avez besoin d'étendre leurs fonctionnalités, vous comprendrez ce qui doit être fait.

The `tp_getattr` handler is called when the object requires an attribute look-up. It is called in the same situations where the `__getattr__()` method of a class would be called.

Voici un exemple :

    static PyObject *
    newdatatype_getattr(newdatatypeobject *obj, char *name)
    {
        if (strcmp(name, "data") == 0)
        {
            return PyLong_FromLong(obj->data);
        }

        PyErr_Format(PyExc_AttributeError,
                     "'%.100s' object has no attribute '%.400s'",
                     Py_TYPE(obj)->tp_name, name);
        return NULL;
    }

The `tp_setattr` handler is called when the `__setattr__()` or `__delattr__()` method of a class instance would be called. When an attribute should be deleted, the third parameter will be `NULL`. Here is an example that simply raises an exception; if this were really all you wanted, the `tp_setattr` handler should be set to `NULL`.

    static int
    newdatatype_setattr(newdatatypeobject *obj, char *name, PyObject *v)
    {
        PyErr_Format(PyExc_RuntimeError, "Read-only attribute: %s", name);
        return -1;
    }

## 3.4. Comparaison des objets

    richcmpfunc tp_richcompare;

The `tp_richcompare` handler is called when comparisons are needed. It is analogous to the rich comparison methods, like `__lt__()`, and also called by `PyObject_RichCompare()` and `PyObject_RichCompareBool()`.

Cette fonction est appelée avec deux objets Python et l'opérateur comme arguments, où l'opérateur est `Py_EQ`, `Py_NE`, `Py_LE`, `Py_GE`, `Py_LT` ou `Py_GT`. Elle doit comparer les deux objets conformément à l'opérateur spécifié et renvoyer `Py_True` ou `Py_False` si la comparaison a réussi, `Py_NotImplemented` pour indiquer que la comparaison n'est pas implémentée et que la méthode de comparaison de l'autre objet doit être essayée, ou `NULL` si une exception doit être levée.

Voici un exemple d'implémentation, pour un type de données où l'égalité signifie que la taille d'un pointeur interne est égale :

    static PyObject *
    newdatatype_richcmp(newdatatypeobject *obj1, newdatatypeobject *obj2, int op)
    {
        PyObject *result;
        int c, size1, size2;

        /* code to make sure that both arguments are of type
           newdatatype omitted */

        size1 = obj1->obj_UnderlyingDatatypePtr->size;
        size2 = obj2->obj_UnderlyingDatatypePtr->size;

        switch (op) {
        case Py_LT: c = size1 <  size2; break;
        case Py_LE: c = size1 <= size2; break;
        case Py_EQ: c = size1 == size2; break;
        case Py_NE: c = size1 != size2; break;
        case Py_GT: c = size1 >  size2; break;
        case Py_GE: c = size1 >= size2; break;
        }
        result = c ? Py_True : Py_False;
        Py_INCREF(result);
        return result;
     }

## 3.5. Gestion de protocoles abstraits

Python prend en charge divers « protocoles » abstraits ; les interfaces spécifiques fournies pour utiliser ces interfaces sont documentées dans Couche d'abstraction des objets.

Un certain nombre de ces interfaces abstraites ont été définies au début du développement de l'implémentation Python. En particulier, les protocoles de nombre, de correspondance et de séquence font partie de Python depuis le début. D'autres protocoles ont été ajoutés au fil du temps. Pour les protocoles qui dépendent de plusieurs routines de gestionnaire de l'implémentation du type, les anciens protocoles ont été définis comme des blocs facultatifs de gestionnaires référencés par l'objet type. Pour les protocoles plus récents, il existe des emplacements supplémentaires dans l'objet de type principal, avec un bit d'indicateur défini pour indiquer que les emplacements sont présents et doivent être vérifiés par l'interpréteur (le bit d'indicateur n'indique pas que les valeurs d'emplacement ne sont pas `NULL` ; il peut être défini pour indiquer la présence d'un emplacement, mais un emplacement peut toujours être vide). :

    PyNumberMethods   *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods  *tp_as_mapping;

Si vous souhaitez que votre objet puisse agir comme un nombre, une séquence ou un tableau de correspondances, placez l'adresse d'une structure qui implémente le type C `PyNumberMethods`, `PySequenceMethods` ou `PyMappingMethods`, respectivement. C'est à vous de remplir cette structure avec les valeurs appropriées. Vous pouvez trouver des exemples d'utilisation de chacun d'entre eux dans le répertoire `Objects` de la distribution source de Python. :

    hashfunc tp_hash;

Cette fonction, si vous la fournissez, doit renvoyer un condensat pour une instance de votre type de données. Voici un exemple simple :

    static Py_hash_t
    newdatatype_hash(newdatatypeobject *obj)
    {
        Py_hash_t result;
        result = obj->some_size + 32767 * obj->some_number;
        if (result == -1)
           result = -2;
        return result;
    }

`Py_hash_t` is a signed integer type with a platform-varying width. Returning `-1` from `tp_hash` indicates an error, which is why you should be careful to avoid returning it when hash computation is successful, as seen above.

    ternaryfunc tp_call;

Cette fonction est appelée lorsqu'une instance de votre type de données est « appelée », par exemple, si `obj1` est une instance de votre type de données et que le script Python contient `obj1('hello')`, le gestionnaire `tp_call` est appelé.

Cette fonction prend trois arguments :

1. self est l'instance du type de données qui fait l'objet de l'appel. Si l'appel est `obj1('hello')`, alors self est `obj1`.

2. args est un n-uplet contenant les arguments de l'appel. Vous pouvez utiliser `PyArg_ParseTuple()` pour extraire les arguments.

3. kwds est le dictionnaire d'arguments nommés qui ont été passés. Si ce n'est pas `NULL` et que vous gérez les arguments nommés, utilisez `PyArg_ParseTupleAndKeywords()` pour extraire les arguments. Si vous ne souhaitez pas prendre en charge les arguments nommés et qu'il n'est pas `NULL`, levez une `TypeError` avec un message indiquant que les arguments nommés ne sont pas pris en charge.

Ceci est une implémentation `tp_call` très simple :

    static PyObject *
    newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *kwds)
    {
        PyObject *result;
        const char *arg1;
        const char *arg2;
        const char *arg3;

        if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
            return NULL;
        }
        result = PyUnicode_FromFormat(
            "Returning -- value: [%d] arg1: [%s] arg2: [%s] arg3: [%s]\n",
            obj->obj_UnderlyingDatatypePtr->size,
            arg1, arg2, arg3);
        return result;
    }

    /* Iterators */
    getiterfunc tp_iter;
    iternextfunc tp_iternext;

These functions provide support for the iterator protocol. Both handlers take exactly one parameter, the instance for which they are being called, and return a new reference. In the case of an error, they should set an exception and return `NULL`. `tp_iter` corresponds to the Python `__iter__()` method, while `tp_iternext` corresponds to the Python `__next__()` method.

Tout objet iterable doit implémenter le gestionnaire `tp_iter`, qui doit renvoyer un objet de type iterator. Ici, les mêmes directives s'appliquent de la même façon que pour les classes Python :

- Pour les collections (telles que les listes et les n-uplets) qui peuvent implémenter plusieurs itérateurs indépendants, un nouvel itérateur doit être créé et renvoyé par chaque appel de type `tp_iter`.

- Les objets qui ne peuvent être itérés qu'une seule fois (généralement en raison d'effets de bord de l'itération, tels que les objets fichiers) peuvent implémenter `tp_iter` en renvoyant une nouvelle référence à eux-mêmes – et doivent donc également implémenter le gestionnaire `tp_iternext`.

Tout objet itérateur doit implémenter à la fois `tp_iter` et `tp_iternext`. Le gestionnaire `tp_iter` d'un itérateur doit renvoyer une nouvelle référence de l'itérateur. Son gestionnaire `tp_iternext` doit renvoyer une nouvelle référence à l'objet suivant dans l'itération, s'il y en a un. Si l'itération a atteint la fin, `tp_iternext` peut renvoyer `NULL` sans définir d'exception, ou il peut définir `StopIteration` en plus de renvoyer `NULL` ; éviter de lever une exception peut donner des performances légèrement meilleures. Si une erreur réelle se produit, `tp_iternext` doit toujours définir une exception et renvoyer `NULL`.

## 3.6. Prise en charge de la référence faible

L'un des objectifs de l'implémentation de la référence faible de Python est de permettre à tout type d'objet de participer au mécanisme de référence faible sans avoir à supporter le surcoût de la performance critique des certains objets, tels que les nombres.

Voir aussi

documentation pour le module `weakref`.

For an object to be weakly referenceable, the extension type must set the `Py_TPFLAGS_MANAGED_WEAKREF` bit of the `tp_flags` field. The legacy `tp_weaklistoffset` field should be left as zero.

Concretely, here is how the statically declared type object would look:

    static PyTypeObject TrivialType = {
        PyVarObject_HEAD_INIT(NULL, 0)
        /* ... other members omitted for brevity ... */
        .tp_flags = Py_TPFLAGS_MANAGED_WEAKREF | ...,
    };

The only further addition is that `tp_dealloc` needs to clear any weak references (by calling `PyObject_ClearWeakRefs()`):

    static void
    Trivial_dealloc(TrivialObject *self)
    {
        /* Clear weakrefs first before calling any destructors */
        PyObject_ClearWeakRefs((PyObject *) self);
        /* ... remainder of destruction code omitted for brevity ... */
        Py_TYPE(self)->tp_free((PyObject *) self);
    }

## 3.7. Plus de suggestions

Pour savoir comment mettre en œuvre une méthode spécifique pour votre nouveau type de données, téléchargez le code source CPython. Allez dans le répertoire `Objects`, puis cherchez dans les fichiers sources C la fonction `tp_` plus la fonction que vous voulez (par exemple, `tp_richcompare`). Vous trouverez des exemples de la fonction que vous voulez implémenter.

Lorsque vous avez besoin de vérifier qu'un objet est une instance concrète du type que vous implémentez, utilisez la fonction `PyObject_TypeCheck()`. Voici un exemple de son utilisation :

    if (!PyObject_TypeCheck(some_object, &MyType)) {
        PyErr_SetString(PyExc_TypeError, "arg #1 not a mything");
        return NULL;
    }

Voir aussi

Télécharger les versions sources de CPython.

  https://www.python.org/downloads/source/

Le projet CPython sur GitHub, où se trouve le code source CPython.

  https://github.com/python/cpython