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;
   } 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 one of the type
codes defined in the "structmember.h" header; 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.

Les constantes de drapeaux suivantes sont définies dans
"structmember.h" ; elles peuvent être combinées à l'aide du *OU*
binaire.

+-----------------------------+------------------------------------------------+
| Constante                   | Signification                                  |
|=============================|================================================|
| "READONLY"                  | Jamais disponible en écriture.                 |
+-----------------------------+------------------------------------------------+
| "PY_AUDIT_READ"             | Émet un événement d'audit "object.__getattr__" |
|                             | avant la lecture.                              |
+-----------------------------+------------------------------------------------+

Modifié dans la version 3.10: "RESTRICTED", "READ_RESTRICTED" and
"WRITE_RESTRICTED" are deprecated. However, "READ_RESTRICTED" is an
alias for "PY_AUDIT_READ", so fields that specify either "RESTRICTED"
or "READ_RESTRICTED" will also raise an audit event.

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,
                    "'%.50s' object has no attribute '%.400s'",
                    tp->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(PyObject *obj1, PyObject *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 *self, 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".

Pour qu'un objet soit faiblement référençable, le type d'extension
doit faire deux choses :

1. inclure un champ PyObject* dans la structure d'objet C dédiée au
   mécanisme de référence faible. Le constructeur de l'objet doit le
   laisser à la valeur "NULL" (ce qui est automatique lorsque l'on
   utilise le champ par défaut "tp_alloc") ;

2. définir le membre de type "tp_weaklistoffset" à la valeur de
   décalage (*offset*) du champ susmentionné dans la structure de
   l'objet *C*, afin que l'interpréteur sache comment accéder à ce
   champ et le modifier.

Concrètement, voici comment une structure d'objet simple serait
complétée par le champ requis :

   typedef struct {
       PyObject_HEAD
       PyObject *weakreflist;  /* List of weak references */
   } TrivialObject;

Et le membre correspondant dans l'objet de type déclaré statiquement :

   static PyTypeObject TrivialType = {
       PyVarObject_HEAD_INIT(NULL, 0)
       /* ... other members omitted for brevity ... */
       .tp_weaklistoffset = offsetof(TrivialObject, weakreflist),
   };

Le seul ajout supplémentaire est que "tp_dealloc" doit effacer toute
référence faible (en appelant "PyObject_ClearWeakRefs()") si le champ
est non "NULL" :

   static void
   Trivial_dealloc(TrivialObject *self)
   {
       /* Clear weakrefs first before calling any destructors */
       if (self->weakreflist != NULL)
           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
