Dans ce tuto nous allons voir comment invoquer des fonctions codées en C dans un programme Python. Ces fonctions vont être associées à un module que nous nommerons « MyModule »
Voici le code Python qui importe ce module et invoque les fonctions natives : notez bien que la seconde fonction déclenche une exception, d'où la présence du try
/ except
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#!/usr/bin/python3 import MyModule # Appel de la première fonction native (example). print("You start with Python Code") print(MyModule.example("Hello")) # Appel de la seconde fonction native levant une exception (example2). try: print(MyModule.example2()) except BaseException as e: print("Trop fort ce Python :", str(e)) # Récupération des docstrings des fonctions print("Doc:", MyModule.example.__doc__) print("Doc:", MyModule.example2.__doc__) |
Il est maintenant temps d'implémenter le module en C. Le fichier de code suivant est découpé en deux parties. La première partie définit les méthodes exposées par le module : il y en a deux, appelées
example
et example2
. Notez aussi qu'une instance, correspondante à l'exception pouvant être levée par example2
, est définie dans cette première partie.
La seconde partie, correspond à la déclaration et l'initialisation de notre module natif : c'est une partie technique qui doit contenir les éléments suivants :
static PyMethodDef functions[]
: le tableau listant les fonctions à exposer en dehors du module. Il est déclaré static
car on ne veut pas qu'il soit manipulable à l'extérieur
du fichier de code C qui le définit. J'ai choisi de l'appeler functions
. Pour chaque entrèe du tableau, vous pouvez spécifier : le nom de la fonction coté Python, le pointeur sur la
fonction C associée, le type de la fonction et la documentation rattachée à cette fonction. Le tableau doit impérativement être terminé par une dernière entrée, qualifiée de « sentinelle »,
qui fixe ses valeurs à NULL
ou 0
: cela permettra de savoir quand le tableau se termine (pour rappel, en C, un tableau ne porte pas sa taille).
static struct PyModuleDef myModule
: une structure de définition du module. Elle comporte le nom du module du point de vue de Python, la documentation rattachée à ce module
(dans notre cas, il n'y en a pas), une configuration sur la gestion de l'état du module et le tableau de définition des fonctions exposés vu ci-dessus.
PyMODINIT_FUNC PyInit_MyModule(void)
: cette fonction est appelée lors de l'initialisation du module. Dans notre exemple, elle commence par créer le module, puis elle crée l'instance
d'exception utilisée par notre module et enfin elle doit renvoyer le module nouvellement créé.
Voici le code C de notre module (fichier MyModule.cpp).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
/* * Ce fichier permet d'accèder à l'environnement Python en cours d'exécution * à partir de votre programme C. Il fournit aussi les différentes fonctions * permettant d'adapter les données entre les deux langages Python <-> C */ #include <Python.h> /* * Une fois importe, le module definit deux fonctions : * - example qui prend une chaine en argument et retourne 0 * - example2 qui leve une exception creee pour l'occasion */ // --- Les fonctions exposées par votre module --- static PyObject* exampleException; static PyObject* example(PyObject* self, PyObject* args) { // Les types Python ne sont pas directement exploitables en C. Par exemple, // un caractère en Python utilise deux octets alors qu'en C, il en occupe // qu'un seul. Il faut donc convertir les paramètres Python au format C. // C'est l'objectif de PyArg_ParseTuple ! const char* c_string; if ( ! PyArg_ParseTuple(args, "s", &c_string) ) return NULL; // On peut maintenant utiliser la chaîne C issue du paramètre Python. printf( "C/C++ code receive %s \n", c_string ); // On renvoie la valeur 0 convertie au format Python. return PyLong_FromLong( 0 ); } static PyObject* example2(PyObject* self, PyObject* args) { printf( "C/C++ code fire an exception\n" ); // Levée d'une exception Python : elle sera rattrapée en Python. PyErr_SetString(exampleException, "Exemple de levée d'erreur en C"); // On ne renvoie donc pas de valeur de retour particulière. return NULL; } // --- Partie configuration du module --- // Tableau des fonctions exposées, avec la documentation. static PyMethodDef functions[] = { {"example", example, METH_VARARGS, "Une fonction"}, {"example2", example2, METH_VARARGS, "Une fonction levant une exception"}, {NULL, NULL, 0, NULL} }; // Données de définition du module static struct PyModuleDef myModule = { PyModuleDef_HEAD_INIT, "MyModule", /* nom du module */ NULL, /* documentation du module, ou NULL si non proposée */ -1, /* -1 => état du module stocké en global */ functions /* Tableau des fonctions exposées */ }; // Initialisation du module PyMODINIT_FUNC PyInit_MyModule(void) { // Création du module PyObject * module = PyModule_Create( &myModule ); if ( module == NULL ) return NULL; // Création d'une instance d'exception exampleException = PyErr_NewException( "MyModule.error", NULL, NULL ); Py_INCREF( exampleException ); PyModule_AddObject( module, "error", exampleException ); // On doit renvoyer le module nouvellement créé return module; } |
Il faut maintenant compiler la partie de code native (en langage C). Et là j'imagine que vous commencez à paniquer. Il ne faut pas ! Trois étapes simples sont requises pour compiler notre module.
Installez un compilateur C sur votre machine. Sur Linux/Unix/Mac, je vous propose d'utiliser GCC : je vous laisse le soin de procéder à l'installation (si nécessaire) en utilisant le gestionnaire de paquets de votre système. Sous Windows, vous pouvez soit utiliser Visual Studio, soit utiliser MinGW-64 (un portage de GCC pour Windows) : à votre convenance.
Python fournit un outil de « build » pour vos modules natifs : le module distutils
. Il vous permet de vous affranchir du lancement du compilateur et de la connaissance des
options de compilation à fournir au compilateur :-). Pour l'utiliser, vous devrez fournir un module Python de compilation. Il est traditionnellement appelé Setup.py
et contient notamment
la liste des fichiers C à compiler. En voici le contenu.
1 2 3 4 5 6 7 8 9 10 |
#!/usr/bin/python3 from distutils.core import setup from distutils.core import Extension setup( name = 'MyModule', version = '1.0', ext_modules = [Extension('MyModule', ['MyModule.c']), ], ) |
Dernière étape, il faut lancer la compilation. Veuillez saisir la ligne de commande suivante : normalement, le module compilé doit être produit.
$> python3 Setup.py build_ext --inplace
Voici les résultats produits par notre exemple.
$> python3 UseModule.py You start with Python Code C/C++ code receive Hello 0 C/C++ code fire an exception Trop fort ce Python : Exemple de levée d'erreur en C Doc: Une fonction Doc: Une fonction levant une exception $>
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :