Using the C API: Assorted topics

The tutorial walked you through creating a C API extension module, but left many areas unexplained. This document looks at several concepts that you'll need to learn in order to write more complex extensions.

Errors and Exceptions

Une convention importante dans l'interpréteur Python est la suivante : lorsqu'une fonction échoue, elle doit définir une condition d'exception et renvoyer une valeur d'erreur (généralement -1 ou un pointeur NULL). Les informations d'exception sont stockées dans trois attributs de l'état du thread de l'interpréteur. Ils valent NULL s'il n'y a pas d'exception. Sinon, ce sont les équivalents C des membres du n-uplet Python renvoyé par sys.exc_info(). Il s'agit du type d'exception, de l'instance d'exception et d'un objet de trace. Il est important de les connaître pour comprendre comment les erreurs sont transmises.

L'API Python définit un certain nombre de fonctions pour créer différents types d'exceptions.

La plus courante est PyErr_SetString(). Ses arguments sont un objet exception et une chaîne C. L'objet exception est généralement un objet prédéfini comme PyExc_ZeroDivisionError. La chaîne C indique la cause de l'erreur et est convertie en une chaîne Python puis stockée en tant que « valeur associée » à l'exception.

Une autre fonction utile est PyErr_SetFromErrno(), qui construit une exception à partir de la valeur de la variable globale errno. La fonction la plus générale est PyErr_SetObject(), qui prend deux arguments : l'exception et sa valeur associée. Vous ne devez pas appliquer Py_INCREF() aux objets transmis à ces fonctions.

Vous pouvez tester de manière non destructive si une exception a été levée avec PyErr_Occurred(). Cela renvoie l'objet exception actuel, ou NULL si aucune exception n'a eu lieu. Cependant, vous ne devriez pas avoir besoin d'appeler PyErr_Occurred() pour voir si une erreur est survenue durant l'appel d'une fonction, puisque vous devriez être en mesure de le déterminer à partir de la valeur renvoyée.

Lorsqu'une fonction f ayant appelé une autre fonction g détecte que cette dernière a échoué, f devrait donner une valeur d'erreur à son tour (habituellement NULL ou -1). La fonction f ne devrait pas appeler l'une des fonctions PyErr_*, l'une d'entre elles ayant déjà été appelée par g. La fonction appelant f est alors censée renvoyer aussi un code d'erreur à celle qui l'a appelée, toujours sans utiliser PyErr_*, et ainsi de suite. La cause la plus détaillée de l'erreur a déjà été signalée par la fonction l'ayant détecté en premier. Une fois l'erreur remontée à la boucle principale de l'interpréteur Python, il interrompt le code en cours d'exécution et essaie de trouver un gestionnaire d'exception spécifié par le développeur Python.

(Il y a des situations où un module peut effectivement donner un message d'erreur plus détaillé en appelant une autre fonction PyErr_* et, dans de tels cas, il est tout à fait possible de le faire. Cependant, ce n'est généralement pas nécessaire, et peut amener à perdre des informations sur la cause de l'erreur : la plupart des opérations peuvent échouer pour tout un tas de raisons.)

Pour ignorer une exception qui aurait été émise lors d'un appel de fonction qui a échoué, l'exception doit être retirée explicitement en appelant PyErr_Clear(). Le seul cas pour lequel du code C devrait appeler PyErr_Clear() est lorsqu'il ne veut pas passer l'erreur à l'interpréteur, mais souhaite la gérer lui-même (peut-être en essayant quelque chose d'autre, ou en prétendant que rien n'a mal tourné).

Chaque échec de malloc() doit être transformé en une exception, l'appelant direct de malloc() (ou realloc()) doit appeler PyErr_NoMemory() et prendre l'initiative de renvoyer une valeur d'erreur. Toutes les fonctions construisant des objets (tels que PyLong_FromLong()) le font déjà, donc cette note ne concerne que ceux qui appellent malloc() directement.

Notez également que, à l'exception notable de PyArg_ParseTuple() et compagnie, les fonctions qui renvoient leur statut sous forme d'entier donnent généralement une valeur positive ou zéro en cas de succès et -1 en cas d'échec, comme les appels du système Unix.

Enfin, lorsque vous renvoyez un code d'erreur, n'oubliez pas faire un brin de nettoyage (en appelant Py_XDECREF() ou Py_DECREF() avec les objets que vous auriez déjà créés) !

The choice of which exception to raise is entirely yours. There are predeclared C objects corresponding to all built-in Python exceptions, such as PyExc_ZeroDivisionError, which you can use directly. Of course, you should choose exceptions wisely --- don't use PyExc_TypeError to mean that a file couldn't be opened (that should probably be PyExc_OSError). If something's wrong with the argument list, the PyArg_ParseTuple() function usually raises PyExc_TypeError. If you have an argument whose value must be in a particular range or must satisfy other conditions, PyExc_ValueError is appropriate.

You can also define a new exception that is unique to your module. The simplest way to do this is to declare a static global object variable at the beginning of the file:

static PyObject *SpamError = NULL;

and initialize it by calling PyErr_NewException() in the module's Py_mod_exec function (spam_module_exec()):

SpamError = PyErr_NewException("spam.error", NULL, NULL);

Since SpamError is a global variable, it will be overwritten every time the module is reinitialized, when the Py_mod_exec function is called.

For now, let's avoid the issue: we will block repeated initialization by raising an ImportError:

static PyObject *SpamError = NULL;

static int
spam_module_exec(PyObject *m)
{
    if (SpamError != NULL) {
        PyErr_SetString(PyExc_ImportError,
                        "cannot initialize spam module more than once");
        return -1;
    }
    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) {
        return -1;
    }

    return 0;
}

static PyModuleDef_Slot spam_module_slots[] = {
    {Py_mod_exec, spam_module_exec},
    {0, NULL}
};

static struct PyModuleDef spam_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "spam",
    .m_size = 0,  // non-negative
    .m_slots = spam_module_slots,
};

PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModuleDef_Init(&spam_module);
}

Note that the Python name for the exception object is spam.error. The PyErr_NewException() function may create a class with the base class being Exception (unless another class is passed in instead of NULL), described in Exceptions natives.

Notez également que la variable SpamError contient une référence à la nouvelle classe créée ; ceci est intentionnel ! Comme l'exception peut être enlevée du module par du code externe, une référence à la classe est nécessaire pour assurer qu'elle ne sera pas supprimée par le ramasse-miettes, entraînant que SpamError devienne un pointeur dans le vide. Si cela se produisait, le code C qui lève cette exception peut engendrer un core dump ou des effets secondaires inattendus.

For now, the Py_DECREF() call to remove this reference is missing. Even when the Python interpreter shuts down, the global SpamError variable will not be garbage-collected. It will "leak". We did, however, ensure that this will happen at most once per process.

We discuss the use of PyMODINIT_FUNC as a function return type later in this sample.

The spam.error exception can be raised in your extension module using a call to PyErr_SetString() as shown below:

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = system(command);
    if (sts < 0) {
        PyErr_SetString(SpamError, "System command failed");
        return NULL;
    }
    return PyLong_FromLong(sts);
}

Embedding an extension

If you want to make your module a permanent part of the Python interpreter, you will have to change the configuration setup and rebuild the interpreter. On Unix, place your file (spammodule.c for example) in the Modules/ directory of an unpacked source distribution, add a line to the file Modules/Setup.local describing your file:

spam spammodule.o

et reconstruisez l'interpréteur en exécutant make dans le répertoire de niveau supérieur. Vous pouvez également exécuter make dans le sous-répertoire Modules/, mais vous devez d'abord reconstruire le Makefile en exécutant « make Makefile » (c'est nécessaire chaque fois que vous modifiez le fichier Setup).

Si votre module nécessite d'être lié à des bibliothèques supplémentaires, celles-ci peuvent être ajoutées à la fin de la ligne de votre module dans le fichier de configuration, par exemple :

spam spammodule.o -lX11

Appeler des fonctions Python en C

The tutorial concentrated on making C functions callable from Python. The reverse is also useful: calling Python functions from C. This is especially the case for libraries that support so-called "callback" functions. If a C interface makes use of callbacks, the equivalent Python often needs to provide a callback mechanism to the Python programmer; the implementation will require calling the Python callback functions from a C callback. Other uses are also imaginable.

Heureusement, l'interpréteur Python est facilement appelé de manière récursive et il existe une interface standard pour appeler une fonction Python (nous ne nous attarderons pas sur la façon d'appeler l'analyseur Python avec une chaîne particulière en entrée — si vous êtes intéressé, jetez un œil à l'implémentation de l'option de ligne de commande -c dans Modules/main.c à partir du code source Python).

L'appel d'une fonction Python est facile. Tout d'abord, le programme Python doit vous transmettre d'une manière ou d'une autre l'objet de fonction Python. Vous devez fournir une fonction (ou une autre interface) pour ce faire. Lorsque cette fonction est appelée, enregistrez un pointeur vers l'objet de la fonction Python (faites attention à Py_INCREF() !) dans une variable globale — ou là où vous le souhaitez. Par exemple, la fonction suivante peut faire partie d'une définition de module :

static PyObject *my_callback = NULL;

static PyObject *
my_set_callback(PyObject *dummy, PyObject *args)
{
    PyObject *result = NULL;
    PyObject *temp;

    if (PyArg_ParseTuple(args, "O:set_callback", &temp)) {
        if (!PyCallable_Check(temp)) {
            PyErr_SetString(PyExc_TypeError, "parameter must be callable");
            return NULL;
        }
        Py_XINCREF(temp);         /* Add a reference to new callback */
        Py_XDECREF(my_callback);  /* Dispose of previous callback */
        my_callback = temp;       /* Remember new callback */
        /* Boilerplate to return "None" */
        Py_INCREF(Py_None);
        result = Py_None;
    }
    return result;
}

This function must be registered with the interpreter using the METH_VARARGS flag in PyMethodDef.ml_flags. The PyArg_ParseTuple() function and its arguments are documented in section Extraire des paramètres dans des fonctions d'extension.

Les macros Py_XINCREF() et Py_XDECREF() incrémentent/décrémentent le compteur des références d'un objet et sont sûres quant à la présence de pointeurs NULL (mais notez que temp ne sera pas NULL dans ce contexte). Plus d'informations à ce sujet dans la section Compteurs de références.

Plus tard, quand il est temps d'appeler la fonction, vous appelez la fonction C PyObject_CallObject(). Cette fonction requiert deux arguments, tous deux des pointeurs vers des objets Python arbitraires : la fonction Python et la liste d'arguments. La liste d'arguments doit toujours être un objet n-uplet, dont la longueur est le nombre d'arguments. Pour appeler la fonction Python sans argument, passez NULL ou le n-uplet vide ; pour l'appeler avec un argument, passez un n-uplet singleton. Py_BuildValue() renvoie un n-uplet lorsque sa chaîne de format se compose de zéro ou plusieurs codes de format entre parenthèses. Par exemple :

int arg;
PyObject *arglist;
PyObject *result;
...
arg = 123;
...
/* Time to call the callback */
arglist = Py_BuildValue("(i)", arg);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);

PyObject_CallObject() renvoie un pointeur d'objet Python : c'est la valeur de retour de la fonction Python. PyObject_CallObject() est « neutre en nombre de références » par rapport à ses arguments. Dans l'exemple, un nouveau n-uplet a été créé pour servir de liste d'arguments, qui est décrémenté (avec Py_DECREF()) immédiatement après l'appel PyObject_CallObject().

La valeur de retour de PyObject_CallObject() est « nouvelle » : soit c'est un tout nouvel objet, soit c'est un objet existant dont le nombre de références a été incrémenté. Donc, à moins que vous ne vouliez l'enregistrer dans une variable globale, vous devriez en quelque sorte décrémenter avec Py_DECREF() le résultat, même (surtout !) si vous n'êtes pas intéressé par sa valeur.

Mais avant de le faire, il est important de vérifier que la valeur renvoyée n'est pas NULL. Si c'est le cas, la fonction Python s'est terminée par une levée d'exception. Si le code C qui a appelé PyObject_CallObject() est appelé depuis Python, il devrait maintenant renvoyer une indication d'erreur à son appelant Python, afin que l'interpréteur puisse afficher la pile d'appels, ou que le code Python appelant puisse gérer l'exception. Si cela n'est pas possible ou souhaitable, l'exception doit être effacée en appelant PyErr_Clear(). Par exemple :

if (result == NULL)
    return NULL; /* Pass error back */
...use result...
Py_DECREF(result);

Selon l'interface souhaitée pour la fonction de rappel Python, vous devrez peut-être aussi fournir une liste d'arguments à PyObject_CallObject(). Dans certains cas, la liste d'arguments est également fournie par le programme Python, par l'intermédiaire de la même interface qui a spécifié la fonction de rappel. Elle peut alors être sauvegardée et utilisée de la même manière que l'objet fonction. Dans d'autres cas, vous pouvez avoir à construire un nouveau n-uplet à passer comme liste d'arguments. La façon la plus simple de faire cela est d'appeler Py_BuildValue(). Par exemple, si vous voulez passer un code d'événement intégral, vous pouvez utiliser le code suivant :

PyObject *arglist;
...
arglist = Py_BuildValue("(l)", eventcode);
result = PyObject_CallObject(my_callback, arglist);
Py_DECREF(arglist);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);

Notez la présence de Py_DECREF(arglist) immédiatement après l'appel, avant la vérification des erreurs ! Notez également qu'à proprement parler, ce code n'est pas complet : Py_BuildValue() peut manquer de mémoire, et cela doit être vérifié.

Vous pouvez également appeler une fonction avec des arguments nommés en utilisant PyObject_Call(), qui accepte les arguments et les arguments nommés. Comme dans l'exemple ci-dessus, nous utilisons Py_BuildValue() pour construire le dictionnaire.

PyObject *dict;
...
dict = Py_BuildValue("{s:i}", "name", val);
result = PyObject_Call(my_callback, NULL, dict);
Py_DECREF(dict);
if (result == NULL)
    return NULL; /* Pass error back */
/* Here maybe use the result */
Py_DECREF(result);

Extraire des paramètres dans des fonctions d'extension

The tutorial uses a "METH_O" function, which is limited to a single Python argument. If you want more, you can use METH_VARARGS instead. With this flag, the C function will receive a tuple of arguments instead of a single object.

For unpacking the tuple, CPython provides the PyArg_ParseTuple() function, declared as follows:

int PyArg_ParseTuple(PyObject *arg, const char *format, ...);

L'argument arg doit être un n-uplet contenant une liste d'arguments passée de Python à une fonction C. L'argument format doit être une chaîne de format, dont la syntaxe est expliquée dans Analyse des arguments et construction des valeurs dans le manuel de référence de l'API Python/C. Les arguments restants doivent être des adresses de variables dont le type est déterminé par la chaîne de format.

For example, to receive a single Python str object and turn it into a C buffer, you would use "s" as the format string:

const char *command;
if (!PyArg_ParseTuple(args, "s", &command)) {
    return NULL;
}

If an error is detected in the argument list, PyArg_ParseTuple() returns NULL (the error indicator for functions returning object pointers); your function may return NULL, relying on the exception set by PyArg_ParseTuple().

Notez que si PyArg_ParseTuple() vérifie que les arguments Python ont les types requis, elle ne peut pas vérifier la validité des adresses des variables C transmises à l'appel : si vous y faites des erreurs, votre code plantera probablement ou au moins écrasera des bits aléatoires en mémoire. Donc soyez prudent !

Notez que toute référence sur un objet Python donnée à l'appelant est une référence empruntée ; ne décrémentez pas son compteur de références !

Quelques exemples d'appels :

#include <Python.h>
int ok;
int i, j;
long k, l;
const char *s;
Py_ssize_t size;

ok = PyArg_ParseTuple(args, ""); /* No arguments */
    /* Python call: f() */
ok = PyArg_ParseTuple(args, "s", &s); /* A string */
    /* Possible Python call: f('whoops!') */
ok = PyArg_ParseTuple(args, "lls", &k, &l, &s); /* Two longs and a string */
    /* Possible Python call: f(1, 2, 'three') */
ok = PyArg_ParseTuple(args, "(ii)s#", &i, &j, &s, &size);
    /* A pair of ints and a string, whose size is also returned */
    /* Possible Python call: f((1, 2), 'three') */
{
    const char *file;
    const char *mode = "r";
    int bufsize = 0;
    ok = PyArg_ParseTuple(args, "s|si", &file, &mode, &bufsize);
    /* A string, and optionally another string and an integer */
    /* Possible Python calls:
       f('spam')
       f('spam', 'w')
       f('spam', 'wb', 100000) */
}
{
    int left, top, right, bottom, h, v;
    ok = PyArg_ParseTuple(args, "((ii)(ii))(ii)",
             &left, &top, &right, &bottom, &h, &v);
    /* A rectangle and a point */
    /* Possible Python call:
       f(((0, 0), (400, 300)), (10, 10)) */
}
{
    Py_complex c;
    ok = PyArg_ParseTuple(args, "D:myfunction", &c);
    /* a complex, also providing a function name for errors */
    /* Possible Python call: myfunction(1+2j) */
}

Paramètres nommés pour des fonctions d'extension

If you also want your function to accept keyword arguments, use the METH_KEYWORDS flag in combination with METH_VARARGS. (METH_KEYWORDS can also be used with other flags; see its documentation for the allowed combinations.)

In this case, the C function should accept a third PyObject * parameter which will be a dictionary of keywords. Use PyArg_ParseTupleAndKeywords() to parse the arguments to such a function.

La fonction PyArg_ParseTupleAndKeywords() est déclarée ainsi :

int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict,
                                const char *format, char * const *kwlist, ...);

Les paramètres arg et format sont identiques à ceux de la fonction PyArg_ParseTuple(). Le paramètre kwdict est le dictionnaire de mots-clés reçu comme troisième paramètre du runtime Python. Le paramètre kwlist est une liste de chaînes de caractères terminée par NULL qui identifie les paramètres ; les noms sont mis en correspondance, de gauche à droite, avec les informations de type de format. En cas de succès du processus, PyArg_ParseTupleAndKeywords() renvoie vrai, sinon elle renvoie faux et lève une exception appropriée.

Note

les n-uplets imbriqués ne peuvent pas être traités lorsqu'on utilise des arguments nommés ! Ceux-ci doivent apparaître dans kwlist, dans le cas contraire une exception TypeError est levée.

Voici un exemple de module qui utilise des arguments nommés, basé sur un exemple de Geoff Philbrick (philbrick@hks.com) :

#define PY_SSIZE_T_CLEAN
#include <Python.h>

static PyObject *
keywdarg_parrot(PyObject *self, PyObject *args, PyObject *keywds)
{
    int voltage;
    const char *state = "a stiff";
    const char *action = "voom";
    const char *type = "Norwegian Blue";

    static char *kwlist[] = {"voltage", "state", "action", "type", NULL};

    if (!PyArg_ParseTupleAndKeywords(args, keywds, "i|sss", kwlist,
                                     &voltage, &state, &action, &type))
        return NULL;

    printf("-- This parrot wouldn't %s if you put %i Volts through it.\n",
           action, voltage);
    printf("-- Lovely plumage, the %s -- It's %s!\n", type, state);

    Py_RETURN_NONE;
}

static PyMethodDef keywdarg_methods[] = {
    /* The cast of the function is necessary since PyCFunction values
     * only take two PyObject* parameters, and keywdarg_parrot() takes
     * three.
     */
    {"parrot", (PyCFunction)(void(*)(void))keywdarg_parrot, METH_VARARGS | METH_KEYWORDS,
     "Print a lovely skit to standard output."},
    {NULL, NULL, 0, NULL}   /* sentinel */
};

Créer des valeurs arbitraires

Cette fonction est le complément de PyArg_ParseTuple(). Elle est déclarée comme suit :

PyObject *Py_BuildValue(const char *format, ...);

Elle reconnaît un ensemble d'unités de format similaires à celles reconnues par PyArg_ParseTuple(), mais les arguments (qui sont les données en entrée de la fonction, et non de la sortie) ne doivent pas être des pointeurs, mais juste des valeurs. Elle renvoie un nouvel objet Python, adapté pour être renvoyé par une fonction C appelée depuis Python.

Une différence avec PyArg_ParseTuple() : alors que cette dernière nécessite que son premier argument soit un n-uplet (puisque les listes d'arguments Python sont toujours représentées comme des n-uplets en interne), Py_BuildValue() ne construit pas toujours un n-uplet. Elle construit un n-uplet uniquement si sa chaîne de format contient deux unités de format ou plus. Si la chaîne de format est vide, elle renvoie None ; si elle contient exactement une unité de format, elle renvoie tout objet décrit par cette unité de format. Pour la forcer à renvoyer un n-uplet de taille 0 ou un, mettez la chaîne de format entre parenthèses.

Exemples (à gauche l'appel, à droite la valeur résultante, en Python) :

Py_BuildValue("")                        None
Py_BuildValue("i", 123)                  123
Py_BuildValue("iii", 123, 456, 789)      (123, 456, 789)
Py_BuildValue("s", "hello")              'hello'
Py_BuildValue("y", "hello")              b'hello'
Py_BuildValue("ss", "hello", "world")    ('hello', 'world')
Py_BuildValue("s#", "hello", 4)          'hell'
Py_BuildValue("y#", "hello", 4)          b'hell'
Py_BuildValue("()")                      ()
Py_BuildValue("(i)", 123)                (123,)
Py_BuildValue("(ii)", 123, 456)          (123, 456)
Py_BuildValue("(i,i)", 123, 456)         (123, 456)
Py_BuildValue("[i,i]", 123, 456)         [123, 456]
Py_BuildValue("{s:i,s:i}",
              "abc", 123, "def", 456)    {'abc': 123, 'def': 456}
Py_BuildValue("((ii)(ii)) (ii)",
              1, 2, 3, 4, 5, 6)          (((1, 2), (3, 4)), (5, 6))

Compteurs de références

Dans les langages comme le C ou le C++, le développeur est responsable de l'allocation dynamique et de la dés-allocation de la mémoire sur le tas. En C, cela se fait à l'aide des fonctions malloc() et free(). En C++, les opérateurs new et delete sont utilisés avec essentiellement la même signification et nous limiterons la discussion suivante au cas du C.

Every block of memory allocated with malloc() should eventually be returned to the pool of available memory by exactly one call to free(). It is important to call free() at the right time. If a block's address is forgotten but free() is not called for it, the memory it occupies cannot be reused until the program terminates. This is called a memory leak. On the other hand, if a program calls free() for a block and then continues to use the block, it creates a conflict with reuse of the block through another malloc() call. This is called using freed memory. It has the same bad consequences as referencing uninitialized data --- core dumps, wrong results, mysterious crashes.

Les causes courantes des fuites de mémoire sont des exécutions inhabituelles du code. Par exemple, une fonction peut allouer un bloc de mémoire, effectuer des calculs, puis libérer le bloc. Plus tard, une modification des exigences de la fonction peut ajouter un test au calcul qui détecte une condition d'erreur et peut sortir prématurément de la fonction. Il est facile d'oublier de libérer le bloc de mémoire alloué lors de cette sortie prématurée, surtout lorsqu'elle est ajoutée ultérieurement au code. De telles fuites, une fois introduites, passent souvent inaperçues pendant longtemps : la sortie d'erreur n'est prise que dans une petite fraction de tous les appels, et la plupart des machines modernes ont beaucoup de mémoire virtuelle, de sorte que la fuite ne devient apparente que dans un processus de longue durée qui utilise fréquemment cette fonction. Par conséquent, il est important d'éviter les fuites en ayant une convention ou une stratégie de codage qui minimise ce type d'erreurs.

Comme Python fait un usage intensif de malloc() et de free(), il a besoin d'une stratégie pour éviter les fuites de mémoire ainsi que l'utilisation de la mémoire libérée. La méthode choisie est appelée le comptage de références. Le principe est simple : chaque objet contient un compteur, qui est incrémenté lorsqu'une référence à l'objet est stockée quelque part, et qui est décrémenté lorsqu'une référence à celui-ci est supprimée. Lorsque le compteur atteint zéro, la dernière référence à l'objet a été supprimée et l'objet est libéré.

Une stratégie alternative est appelée ramasse-miettes automatique (automatic garbage collection en anglais). Parfois, le comptage des références est également appelé stratégie de ramasse-miettes, d'où l'utilisation du terme « automatique » pour distinguer les deux. Le grand avantage du ramasse-miettes est que l'utilisateur n'a pas besoin d'appeler free() explicitement (un autre avantage important est l'amélioration de la vitesse ou de l'utilisation de la mémoire, ce n'est cependant pas un fait avéré). L'inconvénient est que pour C, il n'y a pas de ramasse-miettes portable proprement-dit, alors que le comptage des références peut être implémenté de façon portable (tant que les fonctions malloc() et free() sont disponibles, ce que la norme C garantit). Peut-être qu'un jour un ramasse-miettes suffisamment portable sera disponible pour C. D'ici là, nous devons utiliser les compteurs de références.

Bien que Python utilise l'implémentation traditionnelle de comptage de références, il contient également un détecteur de cycles qui fonctionne pour détecter les cycles de références. Cela permet aux applications de ne pas se soucier de la création de références circulaires directes ou indirectes ; celles-ci sont les faiblesses des ramasse-miettes utilisant uniquement le comptage de références. Les cycles de références sont constitués d'objets qui contiennent des références (éventuellement indirectes) à eux-mêmes, de sorte que chaque objet du cycle a un compteur de références qui n'est pas nul. Les implémentations typiques de comptage de références ne sont pas capables de récupérer la mémoire des objets appartenant à un cycle de références, ou référencés à partir des objets dans le cycle, même s'il n'y a pas d'autre référence au cycle lui-même.

Le détecteur de cycle est capable de détecter les cycles isolés et peut récupérer la mémoire afférente. Le module gc expose un moyen d'exécuter le détecteur (la fonction collect()), ainsi que des interfaces de configuration et la possibilité de désactiver le détecteur au moment de l'exécution.

Comptage de références en Python

Il existe deux macros, Py_INCREF(x) et Py_DECREF(x), qui gèrent l'incrémentation et la décrémentation du comptage de références. Py_DECREF() libère également l'objet lorsque le compteur atteint zéro. Pour plus de flexibilité, il n'appelle pas free() directement — plutôt, il fait un appel à travers un pointeur de fonction dans l'objet type objet de l'objet. À cette fin (et pour d'autres), chaque objet contient également un pointeur vers son objet type.

La grande question demeure maintenant : quand utiliser Py_INCREF(x) et Py_DECREF(x) ? Commençons par définir quelques termes. Personne ne possède un objet, mais vous pouvez en avoir une référence. Le compteur de références d'un objet est maintenant défini comme étant le nombre de références à cet objet. Le propriétaire d'une référence est responsable d'appeler Py_DECREF() lorsque la référence n'est plus nécessaire. La possession d'une référence peut être transférée. Il y a trois façons de se débarrasser d'une référence que l'on possède : la transmettre, la stocker ou appeler Py_DECREF(). Oublier de se débarrasser d'une référence crée une fuite de mémoire.

It is also possible to borrow [1] a reference to an object. The borrower of a reference should not call Py_DECREF(). The borrower must not hold on to the object longer than the owner from which it was borrowed. Using a borrowed reference after the owner has disposed of it risks using freed memory and should be avoided completely [2].

L'avantage d'emprunter, plutôt qu'être propriétaire d'une référence, est que vous n'avez pas à vous soucier de libérer la référence sur tous les chemins possibles dans le code — en d'autres termes, avec une référence empruntée, vous ne courez pas le risque de fuites lors d'une sortie prématurée. L'inconvénient de l'emprunt par rapport à la possession est qu'il existe certaines situations subtiles où, dans un code apparemment correct, une référence empruntée peut être utilisée après que le propriétaire auquel elle a été empruntée l'a en fait éliminée.

Une référence empruntée peut être changée en une référence possédée en appelant Py_INCREF(). Cela n'affecte pas le statut du propriétaire à qui la référence a été empruntée — cela crée une nouvelle référence possédée et donne l'entière responsabilité au propriétaire (le nouveau propriétaire doit libérer la référence correctement, ainsi que le propriétaire précédent).

Règles concernant la possession de références

Chaque fois qu'une référence d'objet est passée à l'intérieur ou à l'extérieur d'une fonction, elle fait partie de la spécification de l'interface de la fonction, peu importe que la possession soit transférée avec la référence ou non.

La plupart des fonctions qui renvoient une référence à un objet transmettent la possession avec la référence. En particulier, toutes les fonctions dont la fonction est de créer un nouvel objet, telles que PyLong_FromLong() et Py_BuildValue(), transmettent la possession au récepteur. Même si l'objet n'est pas réellement nouveau, vous recevez toujours la possession d'une nouvelle référence à cet objet. Par exemple, PyLong_FromLong() maintient un cache des valeurs souvent utilisées et peut renvoyer une référence à un élément mis en cache.

De nombreuses fonctions qui extraient des objets d'autres objets transfèrent également la possession avec la référence, par exemple PyObject_GetAttrString(). L'image est moins claire ici, cependant, puisque quelques routines courantes sont des exceptions : PyTuple_GetItem(), PyList_GetItem(), PyDict_GetItem() et PyDict_GetItemString() renvoient toutes des références qui sont empruntées au n-uplet, à la liste ou au dictionnaire.

La fonction PyImport_AddModule() renvoie également une référence empruntée, même si elle peut en fait créer l'objet qu'elle renvoie : c'est possible car une référence à l'objet est stockée dans sys.modules.

Lorsque vous passez une référence d'objet dans une autre fonction, en général, la fonction vous emprunte la référence — si elle a besoin de la stocker, elle utilise Py_INCREF() pour devenir un propriétaire indépendant. Il existe exactement deux exceptions importantes à cette règle : PyTuple_SetItem() et PyList_SetItem(). Ces fonctions s'approprient l'élément qui leur est transmis, même en cas d'échec ! (Notez que PyDict_SetItem() et compagnie ne prennent pas la possession de l'objet, elles sont « normales ».)

Lorsqu'une fonction C est appelée depuis Python, elle emprunte à l'appelant des références aux arguments. L'appelant possède une référence à l'objet, de sorte que la durée de vie de la référence empruntée est garantie jusqu'au retour de la fonction. Ce n'est que lorsqu'une telle référence empruntée doit être stockée ou transmise qu'elle doit être transformée en référence possédée en appelant Py_INCREF().

La référence d'objet renvoyée par une fonction C appelée depuis Python doit être une référence détenue en propre — la possession est transférée de la fonction à son appelant.

Terrain dangereux

Il existe quelques situations où l'utilisation apparemment inoffensive d'une référence empruntée peut entraîner des problèmes. Tous ces problèmes sont en lien avec des invocations implicites de l’interpréteur et peuvent amener le propriétaire d'une référence à s'en défaire.

Le premier cas, et le plus important à connaître, est celui de l'utilisation de Py_DECREF() à un objet non relié lors de l'emprunt d'une référence à un élément de liste. Par exemple :

void
bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    PyList_SetItem(list, 1, PyLong_FromLong(0L));
    PyObject_Print(item, stdout, 0); /* BUG! */
}

Cette fonction emprunte d'abord une référence à list[0], puis remplace list[1] par la valeur 0, et enfin affiche la référence empruntée. Ça a l'air inoffensif, n'est-ce pas ? Mais ce n'est pas le cas !

Let's follow the control flow into PyList_SetItem(). The list owns references to all its items, so when item 1 is replaced, it has to dispose of the original item 1. Now let's suppose the original item 1 was an instance of a user-defined class, and let's further suppose that the class defined a __del__() method. If this class instance has a reference count of 1, disposing of it will call its __del__() method. Internally, PyList_SetItem() calls Py_DECREF() on the replaced item, which invokes replaced item's corresponding tp_dealloc function. During deallocation, tp_dealloc calls tp_finalize, which is mapped to the __del__() method for class instances (see PEP 442). This entire sequence happens synchronously within the PyList_SetItem() call.

Since it is written in Python, the __del__() method can execute arbitrary Python code. Could it perhaps do something to invalidate the reference to item in bug()? You bet! Assuming that the list passed into bug() is accessible to the __del__() method, it could execute a statement to the effect of del list[0], and assuming this was the last reference to that object, it would free the memory associated with it, thereby invalidating item.

La solution, une fois que vous connaissez la source du problème, est simple : incrémentez temporairement le compteur de références. La version correcte de la fonction est :

void
no_bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);

    Py_INCREF(item);
    PyList_SetItem(list, 1, PyLong_FromLong(0L));
    PyObject_Print(item, stdout, 0);
    Py_DECREF(item);
}

This is a true story. An older version of Python contained variants of this bug and someone spent a considerable amount of time in a C debugger to figure out why his __del__() methods would fail...

The second case of problems with a borrowed reference is a variant involving threads. Normally, multiple threads in the Python interpreter can't get in each other's way, because there is a global lock protecting Python's entire object space. However, it is possible to temporarily release this lock using the macro Py_BEGIN_ALLOW_THREADS, and to re-acquire it using Py_END_ALLOW_THREADS. This is common around blocking I/O calls, to let other threads use the processor while waiting for the I/O to complete. Obviously, the following function has the same problem as the previous one:

void
bug(PyObject *list)
{
    PyObject *item = PyList_GetItem(list, 0);
    Py_BEGIN_ALLOW_THREADS
    ...some blocking I/O call...
    Py_END_ALLOW_THREADS
    PyObject_Print(item, stdout, 0); /* BUG! */
}

Pointeurs NULL

En général, les fonctions qui prennent des références d'objets comme arguments ne sont pas conçues pour recevoir des pointeurs NULL, et si vous en donnez comme arguments, elles causeront une erreur de segmentation (ou provoqueront des core dump ultérieurs). Les fonctions qui renvoient des références d'objets renvoient généralement NULL uniquement pour indiquer qu'une exception s'est produite. La raison pour laquelle les arguments NULL ne sont pas testés est que les fonctions passent souvent les objets qu'elles reçoivent à d'autres fonctions, si chaque fonction devait tester pour NULL, il y aurait beaucoup de tests redondants et le code s'exécuterait plus lentement.

Il est préférable de tester la présence de NULL uniquement au début : lorsqu'un pointeur qui peut être NULL est reçu, par exemple, de malloc() ou d'une fonction qui peut lever une exception.

Les macros Py_INCREF() et Py_DECREF() ne vérifient pas les pointeurs NULL. Cependant, leurs variantes Py_XINCREF() et Py_XDECREF() le font.

Les macros de vérification d'un type d'objet particulier (Pytype_Check()) ne vérifient pas les pointeurs NULL — encore une fois, il y a beaucoup de code qui en appelle plusieurs à la suite pour tester un objet par rapport à différents types attendus, ce qui générerait des tests redondants. Il n'y a pas de variantes avec la vérification NULL.

The C function calling mechanism guarantees that the argument list passed to C functions (args in the examples) is never NULL --- in fact it guarantees that it is always a tuple [3].

C'est une grave erreur de laisser un pointeur NULL « s'échapper » vers l'utilisateur Python.

Écrire des extensions en C++

C'est possible d'écrire des modules d'extension en C++, mais sous certaines conditions. Si le programme principal (l'interpréteur Python) est compilé et lié par le compilateur C, les objets globaux ou statiques avec les constructeurs ne peuvent pas être utilisés. Ceci n'est pas un problème si le programme principal est relié par le compilateur C++. Les fonctions qui seront appelées par l'interpréteur Python (en particulier, les fonctions d'initialisation des modules) doivent être déclarées en utilisant extern "C". Il n'est pas nécessaire d'inclure les fichiers d'en-tête Python dans le extern "C" {…}, car ils utilisent déjà ce format si le symbole __cplusplus est défini (tous les compilateurs C++ récents définissent ce symbole).

Fournir une API en langage C pour un module d'extension

De nombreux modules d'extension fournissent simplement de nouvelles fonctions et de nouveaux types à utiliser à partir de Python, mais parfois le code d'un module d'extension peut être utile pour d'autres modules d'extension. Par exemple, un module d'extension peut mettre en œuvre un type « collection » qui fonctionne comme des listes sans ordre. Tout comme le type de liste Python standard possède une API C qui permet aux modules d'extension de créer et de manipuler des listes, ce nouveau type de collection devrait posséder un ensemble de fonctions C pour une manipulation directe à partir d'autres modules d'extension.

À première vue, cela semble facile : il suffit d'écrire les fonctions (sans les déclarer « statiques », bien sûr), de fournir un fichier d'en-tête approprié et de documenter l'API C. Et en fait, cela fonctionnerait si tous les modules d'extension étaient toujours liés statiquement avec l'interpréteur Python. Cependant, lorsque les modules sont utilisés comme des bibliothèques partagées, les symboles définis dans un module peuvent ne pas être visibles par un autre module. Les détails de la visibilité dépendent du système d'exploitation ; certains systèmes utilisent un espace de noms global pour l'interpréteur Python et tous les modules d'extension (Windows, par exemple), tandis que d'autres exigent une liste explicite des symboles importés au moment de la liaison des modules (AIX en est un exemple), ou offrent un choix de stratégies différentes (la plupart des Unix). Et même si les symboles sont globalement visibles, le module dont on souhaite appeler les fonctions n'est peut-être pas encore chargé !

Portability therefore requires not to make any assumptions about symbol visibility. This means that all symbols in extension modules should be declared static, except for the module's initialization function, in order to avoid name clashes with other extension modules. And it means that symbols that should be accessible from other extension modules must be exported in a different way.

Python fournit un mécanisme spécial pour transmettre des informations de niveau C (pointeurs) d'un module d'extension à un autre : les Capsules. Une capsule est un type de données Python qui stocke un pointeur (void*). Les capsules ne peuvent être créées et accessibles que via leur API C, mais elles peuvent être transmises comme n'importe quel autre objet Python. En particulier, elles peuvent être affectées à un nom dans l'espace de noms d'un module d'extension. D'autres modules d'extension peuvent alors importer ce module, récupérer la valeur de ce nom, puis récupérer le pointeur de la Capsule.

Il existe de nombreuses façons d'utiliser les Capsules pour exporter l'API C d'un module d'extension. Chaque fonction peut obtenir sa propre Capsule, ou tous les pointeurs de l'API C peuvent être stockés dans un tableau dont l'adresse est inscrite dans une Capsule. Et les différentes tâches de stockage et de récupération des pointeurs peuvent être réparties de différentes manières entre le module fournissant le code et les modules clients.

Quelle que soit la méthode que vous choisissez, il est important de bien nommer vos Capsules. La fonction PyCapsule_New() prend un paramètre nommé (const char*). Vous êtes autorisé à passer un nom NULL, mais nous vous encourageons vivement à spécifier un nom. Des Capsules correctement nommées offrent un certain degré de sécurité concernant un éventuel conflit de types, car il n'y a pas de moyen de distinguer deux ou plusieurs Capsules non nommées entre elles.

En particulier, les capsules utilisées pour exposer les API C doivent recevoir un nom suivant cette convention :

modulename.attributename

La fonction de commodité PyCapsule_Import() permet de charger facilement une API C fournie via une Capsule, mais seulement si le nom de la Capsule correspond à cette convention. Ce comportement donne aux utilisateurs d'API C un degré élevé de certitude que la Capsule qu'ils chargent contient l'API C correcte.

L'exemple suivant montre une approche qui fait peser la plus grande partie de la charge sur le rédacteur du module d'exportation, ce qui est approprié pour les modules de bibliothèque couramment utilisés. Il stocke tous les pointeurs de l'API C (un seul dans l'exemple !) dans un tableau de pointeurs void qui devient la valeur d'une Capsule. Le fichier d'en-tête correspondant au module fournit une macro qui se charge d'importer le module et de récupérer ses pointeurs d'API C. Les modules clients n'ont qu'à appeler cette macro avant d'accéder à l'API C.

The exporting module is a modification of the spam module from the tutorial. The function spam.system() does not call the C library function system() directly, but a function PySpam_System(), which would of course do something more complicated in reality (such as adding "spam" to every command). This function PySpam_System() is also exported to other extension modules.

The function PySpam_System() is a plain C function, declared static like everything else:

static int
PySpam_System(const char *command)
{
    return system(command);
}

The function spam_system() is modified in a trivial way:

static PyObject *
spam_system(PyObject *self, PyObject *args)
{
    const char *command;
    int sts;

    if (!PyArg_ParseTuple(args, "s", &command))
        return NULL;
    sts = PySpam_System(command);
    return PyLong_FromLong(sts);
}

Au début du module, immédiatement après la ligne :

#include <Python.h>

on doit ajouter deux lignes supplémentaires :

#define SPAM_MODULE
#include "spammodule.h"

The #define is used to tell the header file that it is being included in the exporting module, not a client module. Finally, the module's mod_exec function must take care of initializing the C API pointer array:

static int
spam_module_exec(PyObject *m)
{
    static void *PySpam_API[PySpam_API_pointers];
    PyObject *c_api_object;

    /* Initialize the C API pointer array */
    PySpam_API[PySpam_System_NUM] = (void *)PySpam_System;

    /* Create a Capsule containing the API pointer array's address */
    c_api_object = PyCapsule_New((void *)PySpam_API, "spam._C_API", NULL);

    if (PyModule_Add(m, "_C_API", c_api_object) < 0) {
        return -1;
    }

    return 0;
}

Note that PySpam_API is declared static; otherwise the pointer array would disappear when PyInit_spam() terminates!

L'essentiel du travail se trouve dans le fichier d'en-tête spammodule.h, qui ressemble à ceci :

#ifndef Py_SPAMMODULE_H
#define Py_SPAMMODULE_H
#ifdef __cplusplus
extern "C" {
#endif

/* Header file for spammodule */

/* C API functions */
#define PySpam_System_NUM 0
#define PySpam_System_RETURN int
#define PySpam_System_PROTO (const char *command)

/* Total number of C API pointers */
#define PySpam_API_pointers 1


#ifdef SPAM_MODULE
/* This section is used when compiling spammodule.c */

static PySpam_System_RETURN PySpam_System PySpam_System_PROTO;

#else
/* This section is used in modules that use spammodule's API */

static void **PySpam_API;

#define PySpam_System \
 (*(PySpam_System_RETURN (*)PySpam_System_PROTO) PySpam_API[PySpam_System_NUM])

/* Return -1 on error, 0 on success.
 * PyCapsule_Import will set an exception if there's an error.
 */
static int
import_spam(void)
{
    PySpam_API = (void **)PyCapsule_Import("spam._C_API", 0);
    return (PySpam_API != NULL) ? 0 : -1;
}

#endif

#ifdef __cplusplus
}
#endif

#endif /* !defined(Py_SPAMMODULE_H) */

All that a client module must do in order to have access to the function PySpam_System() is to call the function (or rather macro) import_spam() in its mod_exec function:

static int
client_module_exec(PyObject *m)
{
    if (import_spam() < 0) {
        return -1;
    }
    /* additional initialization can happen here */
    return 0;
}

Le principal inconvénient de cette approche est que le fichier spammodule.h est assez compliqué. Cependant, la structure de base est la même pour chaque fonction exportée, ce qui fait qu'elle ne doit être apprise qu'une seule fois.

Enfin, il convient de mentionner que les Capsules offrent des fonctionnalités supplémentaires, qui sont particulièrement utiles pour l'allocation de la mémoire et la dés-allocation du pointeur stocké dans un objet Capsule. Les détails sont décrits dans le manuel de référence de l'API Python/C dans la section Capsules et dans l'implémentation des Capsules (fichiers Include/pycapsule.h et Objects/pycapsule.c dans la distribution du code source Python).

Notes