1. Étendre Python en C ou C++¶
Il est relativement facile d'ajouter de nouveaux modules à Python, si vous savez programmer en C. Ces modules d'extension permettent deux choses qui ne sont pas possibles directement en Python : ils peuvent définir de nouveaux types natifs et peuvent appeler des fonctions de bibliothèques C ou faire des appels systèmes.
Pour gérer les extensions, l'API Python (Application Programmer Interface) définit un ensemble de fonctions, macros et variables qui donnent accès à la plupart des aspects du système d'exécution de Python. L'API Python est incorporée dans un fichier source C en incluant l'en-tête Python.h
.
La compilation d'un module d'extension dépend de l'usage prévu et de la configuration du système, plus de détails peuvent être trouvés dans les chapitres suivants.
Note
l'interface d'extension C est spécifique à CPython, et les modules d'extension ne fonctionnent pas sur les autres implémentations de Python. Dans de nombreux cas, il est possible d'éviter la rédaction des extensions en C et ainsi préserver la portabilité vers d'autres implémentations. Par exemple, si vous devez appeler une fonction de la bibliothèque C ou faire un appel système, vous devriez envisager d'utiliser le module ctypes
ou d'utiliser la bibliothèque cffi plutôt que d'écrire du code C sur mesure. Ces modules vous permettent d'écrire du code Python s'interfaçant avec le code C et sont plus portables entre les implémentations de Python que l'écriture et la compilation d'une d'extension C.
1.1. Un exemple simple¶
Créons un module d'extension appelé spam
(la nourriture préférée de fans des Monty Python) et disons que nous voulons créer une interface Python à la fonction de la bibliothèque C system()
[1]. Cette fonction prend une chaîne de caractères à terminaison nulle comme argument et renvoie un entier. Nous voulons que cette fonction soit appelable à partir de Python comme suit :
>>> import spam
>>> status = spam.system("ls -l")
Commençons par créer un fichier spammodule.c
(historiquement, si un module se nomme spam
, le fichier C contenant son implémentation est appelé spammodule.c
; si le nom du module est très long, comme spammify
, le nom du module peut être juste spammify.c
).
Les deux premières lignes de notre fichier peuvent être :
#define PY_SSIZE_T_CLEAN
#include <Python.h>
qui récupère l'API Python (vous pouvez ajouter un commentaire décrivant le but du module et un avis de droit d'auteur si vous le souhaitez).
Note
il est possible que Python déclare certaines définitions pré-processeur qui affectent les têtes standards sur certains systèmes, vous devez donc inclure Python.h
avant les en-têtes standards.
#define PY_SSIZE_T_CLEAN
was used to indicate that Py_ssize_t
should be
used in some APIs instead of int
.
It is not necessary since Python 3.13, but we keep it here for backward compatibility.
See Chaînes et tampons for a description of this macro.
Tous les symboles exposés par Python.h
sont préfixés de Py
ou PY
, sauf ceux qui sont définis dans les en-têtes standards. Pour le confort, et comme ils sont largement utilisés par l'interpréteur Python, "Python.h"
inclut lui-même quelques en-têtes standards : <stdio.h>
, <string.h>
, <errno.h>
et <stdlib.h>
. Si ce dernier n'existe pas sur votre système, il déclare les fonctions malloc()
, free()
et realloc()
directement.
La prochaine chose que nous ajoutons à notre fichier de module est la fonction C qui sera appelée lorsque l'expression Python spam.system(chaîne)
sera évaluée (nous verrons bientôt comment elle finit par être appelée) :
static PyObject *
spam_system(PyObject *self, PyObject *args)
{
const char *command;
int sts;
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
sts = system(command);
return PyLong_FromLong(sts);
}
Il y a une correspondance directe de la liste des arguments en Python (par exemple, l'expression "ls -l"
) aux arguments passés à la fonction C. La fonction C a toujours deux arguments, appelés par convention self et args.
Pour les fonctions au niveau du module, l'argument self pointe sur l'objet module, pour une méthode, il pointe sur l'instance de l'objet.
L'argument args sera un pointeur vers un n-uplet Python contenant les arguments. Chaque élément du n-uplet correspond à un argument dans la liste des arguments de l'appel. Les arguments sont des objets Python, afin d'en faire quelque chose dans notre fonction C, nous devons les convertir en valeurs C. La fonction PyArg_ParseTuple()
de l'API Python vérifie les types des arguments et les convertit en valeurs C. Elle utilise un modèle sous forme de chaîne pour déterminer les types requis des arguments ainsi que les types de variables C dans lequel stocker les valeurs converties. Nous approfondirons ceci plus tard.
PyArg_ParseTuple()
renvoie vrai (pas zéro) si tous les arguments ont le bon type et que ses composants ont été stockés dans les variables dont les adresses ont été données en entrée. Elle renvoie faux (zéro) si une liste d'arguments invalide a été passée. Dans ce dernier cas, elle lève également une exception appropriée de sorte que la fonction d'appel puisse renvoyer NULL
immédiatement (comme nous l'avons vu dans l'exemple).
1.2. Intermezzo : les erreurs et les 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.
Vous pouvez également créer une exception spécifique à votre module. Pour cela, déclarez simplement une variable statique au début de votre fichier :
static PyObject *SpamError;
and initialize it in your module's initialization function (PyInit_spam()
)
with an exception object:
PyMODINIT_FUNC
PyInit_spam(void)
{
PyObject *m;
m = PyModule_Create(&spammodule);
if (m == NULL)
return NULL;
SpamError = PyErr_NewException("spam.error", NULL, NULL);
if (PyModule_AddObjectRef(m, "error", SpamError) < 0) {
Py_CLEAR(SpamError);
Py_DECREF(m);
return NULL;
}
return m;
}
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.
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);
}
1.3. Retour vers l'exemple¶
En revenant vers notre fonction exemple, vous devriez maintenant être capable de comprendre cette affirmation :
if (!PyArg_ParseTuple(args, "s", &command))
return NULL;
Elle renvoie NULL
(l'indicateur d'erreur pour les fonctions renvoyant des pointeurs d'objet) si une erreur est détectée dans la liste des arguments, se fiant à l'exception définie par PyArg_ParseTuple()
. Autrement, la valeur chaîne de l'argument a été copiée dans la variable locale command
. Il s'agit d'une attribution de pointeur et vous n'êtes pas supposés modifier la chaîne vers laquelle il pointe (donc en C Standard, la variable command
doit être clairement déclarée comme const char *command
).
La prochaine instruction est un appel à la fonction Unix system()
, en lui passant la chaîne que nous venons d'obtenir à partir de PyArg_ParseTuple()
:
sts = system(command);
Notre fonction spam.system()
doit renvoyer la valeur de sts
comme un objet Python. Cela est effectué par l'utilisation de la fonction PyLong_FromLong()
.
return PyLong_FromLong(sts);
Dans ce cas, elle renvoie un objet de type entier (oui, même les entiers sont des objets, stockés dans le tas, en Python !).
Si vous avez une fonction C qui ne renvoie aucun argument utile (une fonction renvoyant void), la fonction Python correspondante doit renvoyer None
. Vous aurez besoin de cet idiome pour cela (qui est implémenté par la macro Py_RETURN_NONE
) :
Py_INCREF(Py_None);
return Py_None;
Py_None
est la dénomination en C pour l'objet spécial Python None
. C'est un authentique objet Python plutôt qu'un pointeur NULL
qui, dans la plupart des situations, signifie qu'une erreur est survenue comme nous l'avons vu.
1.4. La fonction d'initialisation et le tableau des méthodes du module¶
I promised to show how spam_system()
is called from Python programs.
First, we need to list its name and address in a "method table":
static PyMethodDef SpamMethods[] = {
...
{"system", spam_system, METH_VARARGS,
"Execute a shell command."},
...
{NULL, NULL, 0, NULL} /* Sentinel */
};
Notez la troisième entrée (METH_VARARGS
). C'est un indicateur du type de convention à utiliser pour la fonction C, à destination de l'interpréteur. Il doit valoir normalement METH_VARARGS
ou METH_VARARGS | METH_KEYWORDS
; la valeur 0
indique qu'une variante obsolète de PyArg_ParseTuple()
est utilisée.
Si seulement METH_VARARGS
est utilisé, la fonction s'attend à ce que les paramètres Python soient passés comme un n-uplet que l'on peut analyser via PyArg_ParseTuple()
; des informations supplémentaires sont fournies plus bas.
Le bit METH_KEYWORDS
peut être mis à un dans le troisième champ si des arguments par mots-clés doivent être passés à la fonction. Dans ce cas, la fonction C doit accepter un troisième paramètre PyObject *
qui est un dictionnaire des mots-clés. Utilisez PyArg_ParseTupleAndKeywords()
pour analyser les arguments d'une telle fonction.
Le tableau des méthodes doit être référencé dans la structure de définition du module :
static struct PyModuleDef spammodule = {
PyModuleDef_HEAD_INIT,
"spam", /* name of module */
spam_doc, /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module,
or -1 if the module keeps state in global variables. */
SpamMethods
};
This structure, in turn, must be passed to the interpreter in the module's
initialization function. The initialization function must be named
PyInit_name()
, where name is the name of the module, and should be the
only non-static
item defined in the module file:
PyMODINIT_FUNC
PyInit_spam(void)
{
return PyModule_Create(&spammodule);
}
Note that PyMODINIT_FUNC
declares the function as PyObject *
return type,
declares any special linkage declarations required by the platform, and for C++
declares the function as extern "C"
.
When the Python program imports module spam
for the first time,
PyInit_spam()
is called. (See below for comments about embedding Python.)
It calls PyModule_Create()
, which returns a module object, and
inserts built-in function objects into the newly created module based upon the
table (an array of PyMethodDef
structures) found in the module definition.
PyModule_Create()
returns a pointer to the module object
that it creates. It may abort with a fatal error for
certain errors, or return NULL
if the module could not be initialized
satisfactorily. The init function must return the module object to its caller,
so that it then gets inserted into sys.modules
.
When embedding Python, the PyInit_spam()
function is not called
automatically unless there's an entry in the PyImport_Inittab
table.
To add the module to the initialization table, use PyImport_AppendInittab()
,
optionally followed by an import of the module:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
int
main(int argc, char *argv[])
{
PyStatus status;
PyConfig config;
PyConfig_InitPythonConfig(&config);
/* Add a built-in module, before Py_Initialize */
if (PyImport_AppendInittab("spam", PyInit_spam) == -1) {
fprintf(stderr, "Error: could not extend in-built modules table\n");
exit(1);
}
/* Pass argv[0] to the Python interpreter */
status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
if (PyStatus_Exception(status)) {
goto exception;
}
/* Initialize the Python interpreter. Required.
If this step fails, it will be a fatal error. */
status = Py_InitializeFromConfig(&config);
if (PyStatus_Exception(status)) {
goto exception;
}
PyConfig_Clear(&config);
/* Optionally import the module; alternatively,
import can be deferred until the embedded script
imports it. */
PyObject *pmodule = PyImport_ImportModule("spam");
if (!pmodule) {
PyErr_Print();
fprintf(stderr, "Error: could not import module 'spam'\n");
}
// ... use Python C API here ...
return 0;
exception:
PyConfig_Clear(&config);
Py_ExitStatusException(status);
}
Note
supprimer des entrées de sys.modules
ou importer des modules compilés dans plusieurs interpréteurs au sein d'un même processus (ou le faire à la suite d'un fork()
sans un exec()
préalable) peut créer des problèmes pour certains modules d'extension. Les auteurs de modules d'extension doivent faire preuve de prudence lorsqu'ils initialisent des structures de données internes.
Un exemple de module plus substantiel est inclus dans la distribution des sources Python sous le nom Modules/xxmodule.c
. Ce fichier peut être utilisé comme modèle ou simplement lu comme exemple.
Note
contrairement à notre exemple de spam
, xxmodule
utilise une initialisation multi-phases (nouveau en Python 3.5), où une structure PyModuleDef est renvoyée à partir de PyInit_spam
, et la création du module est laissée au mécanisme d'importation. Pour plus de détails sur l'initialisation multi-phases, voir PEP 489.
1.5. Compilation et liaison¶
Il y a encore deux choses à faire avant de pouvoir utiliser votre nouvelle extension : la compiler et la lier au système Python. Si vous utilisez le chargement dynamique, les détails peuvent dépendre du style de chargement dynamique utilisé par votre système ; voir les chapitres sur la compilation de modules d'extension (chapitre Construire des extensions C et C++) et les informations supplémentaires concernant uniquement la construction sous Windows (chapitre Construire des extensions C et C++ sur Windows) pour plus d'informations à ce sujet.
Si vous ne pouvez pas utiliser le chargement dynamique, ou si vous voulez faire de votre module une partie permanente de l'interpréteur Python, vous devez modifier la configuration et reconstruire l'interpréteur. Heureusement, c'est très simple sous Unix : placez simplement votre fichier (spammodule.c
par exemple) dans le répertoire Modules/
d'une distribution source décompressée, ajoutez une ligne au fichier Modules/Setup.local
décrivant votre fichier :
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
1.6. Appeler des fonctions Python en C¶
Jusqu'à présent, nous nous sommes concentrés sur le fait de rendre les fonctions C appelables depuis Python. L'inverse est également utile : appeler des fonctions Python depuis C. C'est notamment le cas pour les bibliothèques qui gèrent les fonctions dites de « rappel » (callback en anglais). Si une interface C utilise des rappels, l'équivalent Python doit souvent fournir un mécanisme de rappel au développeur Python ; l'implémentation nécessite d'appeler les fonctions de rappel Python à partir d'un rappel C. D'autres utilisations sont également envisageables.
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;
}
Cette fonction doit être déclarée en utilisant le drapeau METH_VARARGS
; ceci est décrit dans la section La fonction d'initialisation et le tableau des méthodes du module. La fonction PyArg_ParseTuple()
et ses arguments sont documentés dans la 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);
1.7. Extraire des paramètres dans des fonctions d'extension¶
La fonction PyArg_ParseTuple()
est déclarée ainsi :
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.
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 :
#define PY_SSIZE_T_CLEAN
#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) */
}
1.8. Paramètres nommés pour des fonctions d'extension¶
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 */
};
static struct PyModuleDef keywdargmodule = {
PyModuleDef_HEAD_INIT,
"keywdarg",
NULL,
-1,
keywdarg_methods
};
PyMODINIT_FUNC
PyInit_keywdarg(void)
{
return PyModule_Create(&keywdargmodule);
}
1.9. 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))
1.10. 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.
1.10.1. 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.
Il est également possible d'emprunter [2] une référence à un objet. L'emprunteur d'une référence ne doit pas appeler Py_DECREF()
. L'emprunteur ne doit pas conserver l'objet plus longtemps que le propriétaire à qui il a été emprunté. L'utilisation d'une référence empruntée après que le propriétaire s'en est débarrassée risque d'utiliser de la mémoire libérée et doit être absolument évitée [3].
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).
1.10.2. 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.
1.10.3. 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.
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...
Le deuxième cas de problèmes liés à une référence empruntée est une variante impliquant des fils d'exécution. Normalement, plusieurs threads dans l'interpréteur Python ne peuvent pas se gêner mutuellement, car il existe un verrou global protégeant tout l'espace objet de Python. Cependant, il est possible de libérer temporairement ce verrou en utilisant la macro Py_BEGIN_ALLOW_THREADS
, et de le ré-acquérir en utilisant Py_END_ALLOW_THREADS
. Ceci est un procédé courant pour bloquer les appels d'entrées/sorties, afin de permettre aux autres threads d'utiliser le processeur en attendant que les E/S soient terminées. Évidemment, la fonction suivante a le même problème que la précédente :
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! */
}
1.10.4. 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
.
Le mécanisme d'appel de fonctions C garantit que la liste d'arguments passée aux fonctions C (args
dans les exemples) n'est jamais NULL
. En fait, il garantit qu'il s'agit toujours d'un n-uplet [4].
C'est une grave erreur de laisser un pointeur NULL
« s'échapper » vers l'utilisateur Python.
1.11. É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).
1.12. 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é !
La portabilité exige donc de ne faire aucune supposition sur la visibilité des symboles. Cela signifie que tous les symboles des modules d'extension doivent être déclarés static
, à l'exception de la fonction d'initialisation du module, afin d'éviter les conflits de noms avec les autres modules d'extension (comme discuté dans la section La fonction d'initialisation et le tableau des méthodes du module). Et cela signifie que les symboles qui devraient être accessibles à partir d'autres modules d'extension doivent être exportés d'une manière différente.
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 section
Un exemple simple. 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"
L'indicateur #define
est utilisé pour indiquer au fichier d'en-tête qu'il est inclus dans le module d'exportation, et non dans un module client. Enfin, la fonction d'initialisation du module doit prendre en charge l'initialisation du tableau de pointeurs de l'API C :
PyMODINIT_FUNC
PyInit_spam(void)
{
PyObject *m;
static void *PySpam_API[PySpam_API_pointers];
PyObject *c_api_object;
m = PyModule_Create(&spammodule);
if (m == NULL)
return NULL;
/* 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) {
Py_DECREF(m);
return NULL;
}
return m;
}
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 initialization function:
PyMODINIT_FUNC
PyInit_client(void)
{
PyObject *m;
m = PyModule_Create(&clientmodule);
if (m == NULL)
return NULL;
if (import_spam() < 0)
return NULL;
/* additional initialization can happen here */
return m;
}
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