Maîtriser la programmation d'extensions Python avec C : Construire des modules natifs rapides
Tout code écrit en langage compilé (C, C++, Java) peut être intégré ou importé dans un script Python. Ce code est considéré comme une extension.
Un module d'extension Python n'est rien d'autre qu'une bibliothèque C normale. Sur Unix, ces bibliothèques se terminent généralement par .so (shared object). Sur Windows, on voit typiquement .dll (dynamically linked library).
Prérequis pour l'écriture d'extensions
Pour commencer, vous avez besoin des fichiers d'en-tête Python.
Sur Unix : installation d'un paquet développeur comme
python2.5-dev.Sur Windows : ces en-têtes sont inclus dans l'installateur binaire Python.
De plus, une bonne connaissance du C ou C++ est nécessaire pour écrire des extensions Python en C.
Premier regard sur une extension Python
Pour votre première extension, regroupez votre code en quatre parties :
Le fichier d'en-tête
Python.h.Les fonctions C à exposer comme interface depuis votre module.
Une table de correspondance reliant les noms de fonctions (vue Python) aux fonctions C dans le module d'extension.
Une fonction d'initialisation.
Le fichier d'en-tête Python.h
Incluez Python.h dans votre fichier source C pour accéder à l'API interne Python et brancher votre module dans l'interpréteur.
Incluez Python.h avant tout autre en-tête. Suivez avec les fonctions à appeler depuis Python.
Les fonctions C
Les signatures des implémentations C de vos fonctions prennent toujours l'une de ces trois formes :
static PyObject *MyFunction(PyObject *self, PyObject *args);
static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *MyFunctionWithNoArgs(PyObject *self);
Chaque déclaration retourne un objet Python. Il n'y a pas de fonction void en Python comme en C. Si vous ne voulez pas retourner de valeur, retournez l'équivalent C de None de Python. Les en-têtes Python définissent la macro Py_RETURN_NONE pour cela.
Les noms de vos fonctions C peuvent être ce que vous voulez car ils ne sont jamais vus hors du module d'extension. Ils sont définis comme static.
Vos fonctions C sont généralement nommées en combinant le nom du module et de la fonction Python :
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Faites votre travail ici. */
Py_RETURN_NONE;
}
C'est une fonction Python appelée func dans le module module. Vous placerez des pointeurs vers vos fonctions C dans la table des méthodes du module.
La table de correspondance des méthodes
Cette table est un tableau de structures PyMethodDef :
struct PyMethodDef {
char *ml_name;
PyCFunction ml_meth;
int ml_flags;
char *ml_doc;
};
Description des membres :
ml_name : Nom de la fonction tel que présenté par l'interpréteur Python.
ml_meth : Adresse d'une fonction avec l'une des signatures décrites.
ml_flags : Indique laquelle des trois signatures
ml_methutilise.- Généralement
METH_VARARGS. - Peut être OU bit à bit avec
METH_KEYWORDSpour arguments nommés. - Peut valoir
METH_NOARGSpour n'accepter aucun argument.
- Généralement
ml_doc : Docstring de la fonction, peut être
NULL.
La table doit se terminer par une sentinelle de valeurs NULL et 0.
Exemple
Pour la fonction ci-dessus, table de correspondance :
static PyMethodDef module_methods[] = {
{"func", (PyCFunction)module_func, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
La fonction d'initialisation
Dernière partie du module d'extension : la fonction d'initialisation. Appelée par l'interpréteur Python au chargement du module. Doit s'appeler initModule, où Module est le nom du module.
La fonction doit être exportée depuis la bibliothèque. Les en-têtes Python définissent PyMODINIT_FUNC pour les incantations appropriées selon l'environnement de compilation.
Structure générale :
PyMODINIT_FUNC initModule() {
Py_InitModule3("func", module_methods, "docstring...");
}
Description de Py_InitModule3 :
func : Fonction à exporter.
module_methods : Nom de la table de correspondance.
docstring : Commentaire pour l'extension.
Ensemble complet :
#include
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Do your stuff here. */
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{"func", (PyCFunction)module_func, METH_NOARGS, NULL},
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC initModule() {
Py_InitModule3("func", module_methods, "docstring...");
}
Exemple complet
#include
static PyObject* helloworld(PyObject* self) {
return Py_BuildValue("s", "Hello, Python extensions!!");
}
static char helloworld_docs[] = "helloworld(): Any message you want to put here!!\n";
static PyMethodDef helloworld_funcs[] = {
{"helloworld", (PyCFunction)helloworld, METH_NOARGS, helloworld_docs},
{NULL}
};
void inithelloworld(void) {
Py_InitModule3("helloworld", helloworld_funcs, "Extension module example!");
}
La fonction Py_BuildValue construit une valeur Python. Sauvegardez dans hello.c. Voyons comment compiler et installer pour l'appeler depuis Python.
Construction et installation d'extensions
Le paquet distutils facilite la distribution de modules Python (purs ou extensions) de façon standard. Les modules sont distribués en source et construits/installés via un script setup.py.
Pour le module ci-dessus, setup.py :
from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',
ext_modules=[Extension('helloworld', ['hello.c'])])
Commande pour compiler, lier et copier la bibliothèque dynamique :
$ python setup.py install
Sur Unix, lancez en root pour écrire dans site-packages. Pas de problème sur Windows.
Importation des extensions
Une fois installé, importez et appelez l'extension dans votre script Python :
#!/usr/bin/python
import helloworld
print(helloworld.helloworld())
Résultat :
Hello, Python extensions!!
Passage de paramètres aux fonctions
Pour définir des fonctions acceptant des arguments, utilisez l'une des autres signatures. Exemple :
static PyObject *module_func(PyObject *self, PyObject *args) {
/* Parse args et faites quelque chose d'intéressant. */
Py_RETURN_NONE;
}
Table des méthodes avec entrée pour la nouvelle fonction :
static PyMethodDef module_methods[] = {
{"func", (PyCFunction)module_func, METH_NOARGS, NULL},
{"func", module_func, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}
};
Utilisez l'API PyArg_ParseTuple pour extraire les arguments du pointeur PyObject passé à votre fonction C.
Le premier argument de PyArg_ParseTuple est args. Le second est une chaîne de format décrivant les arguments attendus. Chaque argument est représenté par un ou plusieurs caractères :
static PyObject *module_func(PyObject *self, PyObject *args) {
int i;
double d;
char *s;
if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
return NULL;
}
/* Faites quelque chose d'intéressant ici. */
Py_RETURN_NONE;
}
Compiler la nouvelle version et l'importer permet d'appeler la fonction avec divers arguments :
module.func(1, s="three", d=2.0)
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)
La fonction PyArg_ParseTuple
Signature standard :
int PyArg_ParseTuple(PyObject* tuple, char* format, ...)
Retourne 0 pour erreur, valeur non-nulle pour succès. tuple est le PyObject* second argument. format est une chaîne C décrivant arguments obligatoires et optionnels.
Codes de format pour PyArg_ParseTuple :
| Code | Type C | Signification |
|---|---|---|
| c | char | Chaîne Python longueur 1 → C char. |
| d | double | Float Python → C double. |
| f | float | Float Python → C float. |
| i | int | Int Python → C int. |
| l | long | Int Python → C long. |
| L | long long | Int Python → C long long. |
| O | PyObject* | Référence empruntée non-NULL vers argument Python. |
| s | char* | Chaîne Python sans nulls embarqués → C char*. |
| s# | char*+int | Toute chaîne Python → adresse C et longueur. |
| t# | char*+int | Tampon lecture-seule segment unique → adresse C et longueur. |
| u | Py_UNICODE* | Unicode Python sans nulls embarqués → C. |
| u# | Py_UNICODE*+int | Tout Unicode Python → adresse C et longueur. |
| w# | char*+int | Tampon lecture/écriture segment unique → adresse C et longueur. |
| z | char* | Comme s, accepte aussi None (met C char* à NULL). |
| z# | char*+int | Comme s#, accepte aussi None. |
| (...) | selon ... | Séquence Python traitée comme un argument par élément. |
| | | Arguments suivants optionnels. | |
| : | Fin format, suivi du nom de fonction pour messages d'erreur. | |
| ; | Fin format, suivi du texte complet du message d'erreur. |
Retour de valeurs
Py_BuildValue prend une chaîne de format comme PyArg_ParseTuple. Au lieu d'adresses, on passe les valeurs réelles. Exemple d'implémentation d'une fonction add :
static PyObject *foo_add(PyObject *self, PyObject *args) {
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("i", a + b);
}
Équivalent Python :
def add(a, b):
return (a + b)
Retourner deux valeurs (capturé comme liste en Python) :
static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
int a, b;
if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
return NULL;
}
return Py_BuildValue("ii", a + b, a - b);
}
Équivalent Python :
def add_subtract(a, b):
return (a + b, a - b)
La fonction Py_BuildValue
Signature standard :
PyObject* Py_BuildValue(char* format, ...)
format décrit l'objet Python à construire. Les arguments suivants sont des valeurs C. Le résultat PyObject* est une nouvelle référence.
Codes couramment utilisés :
| Code | Type C | Signification |
|---|---|---|
| c | char | C char → chaîne Python longueur 1. |
| d | double | C double → float Python. |
| f | float | C float → float Python. |
| i | int | C int → int Python. |
| l | long | C long → int Python. |
| N | PyObject* | Passe objet Python et vole une référence. |
| O | PyObject* | Passe objet Python et INCREF normalement. |
| O& | convert+void* | Conversion arbitraire. |
| s | char* | C char* terminé par null → chaîne Python, ou NULL → None. |
| s# | char*+int | C char* et longueur → chaîne Python, ou NULL → None. |
| u | Py_UNICODE* | Chaîne large C terminée par null → Unicode Python, ou NULL → None. |
| u# | Py_UNICODE*+int | Chaîne large C et longueur → Unicode Python, ou NULL → None. |
| w# | char*+int | Tampon lecture/écriture segment unique → adresse C et longueur. |
| z | char* | Comme s, accepte None (met C char* à NULL). |
| z# | char*+int | Comme s#, accepte None. |
| (...) | selon ... | Construit tuple Python depuis valeurs C. |
| [...] | selon ... | Construit liste Python depuis valeurs C. |
| {...} | selon ... | Construit dictionnaire Python depuis valeurs C, clés et valeurs alternées. |
{...} construit des dictionnaires depuis un nombre pair de valeurs C, alternant clés et valeurs. Exemple : Py_BuildValue("{issi}", 23, "zig", "zag", 42) renvoie un dictionnaire comme {23: 'zig', 'zag': 42} en Python.
Python
- Maîtriser la programmation orientée objet Python : classes, héritage, encapsulation et polymorphisme
- Fonction Python print() : Guide pratique avec exemples
- Maîtriser la méthode strip() de Python : Guide complet et exemples pratiques
- Maîtriser str.count() de Python : Compter caractères et sous-chaînes avec exemples
- Maîtriser les chaînes format() en Python avec exemples clairs
- Maîtriser la méthode String.find() de Python : Syntaxe, Exemples et Alternatives
- Maîtriser les fonctions lambda Python : exemples pratiques et bonnes pratiques
- Fonction Python round() expliquée avec exemples pratiques
- Maîtriser la fonction Python map() : syntaxe, exemples et bonnes pratiques