Outils pour utilisateurs

Outils du site


exemple_python_cpp

Exemple d'appel direct de C++ par Python

Objectif

Python prévoit de pouvoir appeler directement un code en C ou en C++ avec distutils. Le mécanisme nécessaire pour ça est assez logique mais assez complexe au niveau syntaxe. La présente page traite un exemple simple en C++.

Documentation pour Python 2.7: http://docs.python.org/2/extending/

C'est écrit pour Python 2.7, mais la conversion pour Python 3.3 ne devrait pas poser de problème.

Documentation pour Python 3.3: http://docs.python.org/3/extending/

Préparation du code C++

On va créer un module contenant 2 fonctions:

  • fact(n): factorielle de n
  • pgcd(a,b): plus grand commun diviseur de a et b (2 nombres entiers)

Ecrits en code C++ simple, voici les codes, placés dans un même fichier: testext.cpp:

// fonction 'fact' en C++
 
int fact(int n) {
    if (n < 2) 
        return(1);
    return (n)*fact(n-1);
    };
 
// fonction 'pgcd' en C++
 
int pgcd(int a, int b) {
    int r;
    while (b!=0) {
        r = a%b;
        a = b;
        b = r;
        }
    return a;
    };

Il va falloir maintenant habiller ces 2 codes pour que l'interpréteur Python soit capable d'importer le module et d'utiliser correctement ces 2 fonctions. Cela va se faire en 4 étapes:

1- inclure l'en-tête Python.h (Sous Windows, elle est livrée avec Python, et se trouve dans C:\Python27\include)

#include "Python.h"

2- placer un code d'initialisation du module, qui sera exécuté par Python lors de l'importation:

PyMODINIT_FUNC inittestext(void) {
    Py_InitModule("testext", testextMethods);
    };

Dans ce code, testext est le nom du module, et testextMethods l'objet qui va décrire tous les objets disponibles du module (voir point suivant)

3- placer le code qui va donner les objets disponibles du module:

static PyMethodDef testextMethods[] = {
        {"fact", testext_fact, METH_VARARGS},
        {"pgcd", testext_pgcd, METH_VARARGS},
        {NULL, NULL},
    };

4- enfin, chaque fonction C++ va être “encapsulée” par les codes suivants:

Pour la fonction “fact”:

// encapsulation de la fonction 'fact' pour utilisation par Python
 
static PyObject * testext_fact(PyObject *self, PyObject *args) {
    int num;
    if (!PyArg_ParseTuple(args, "i", &num))
        return NULL;
    return (PyObject*)Py_BuildValue("i", fact(num));
    };

Et pour la fonction “pgcd”:

// encapsulation de la fonction 'pgcd' pour utilisation par Python
 
static PyObject * testext_pgcd(PyObject *self, PyObject *args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b))
        return NULL; // cas où a ou b n'est pas un int
    return (PyObject*)Py_BuildValue("i", pgcd(a,b));
    };

Et c'est tout! Voilà le code complet du module “testext.cpp

// creation d'un module en C++ pour importation dans Python
 
#include "Python.h"
 
//###########################################################################
// fonction 'fact' en C++
 
int fact(int n) {
    if (n < 2) 
        return(1);
    return (n)*fact(n-1);
    };
 
//===========================================================================
// encapsulation de la fonction 'fact' pour utilisation par Python
 
static PyObject * testext_fact(PyObject *self, PyObject *args) {
    int num;
    if (!PyArg_ParseTuple(args, "i", &num))
        return NULL;
    return (PyObject*)Py_BuildValue("i", fact(num));
    };
 
//###########################################################################
// fonction 'pgcd' en C++
 
int pgcd(int a, int b) {
    int r;
    while (b!=0) {
        r = a%b;
        a = b;
        b = r;
        }
    return a;
    };
 
//===========================================================================
// encapsulation de la fonction 'pgcd' pour utilisation par Python
 
static PyObject * testext_pgcd(PyObject *self, PyObject *args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b))
        return NULL; // cas où a ou b n'est pas un int
    return (PyObject*)Py_BuildValue("i", pgcd(a,b));
    };
 
//###########################################################################
// liste des objets disponibles du module
 
static PyMethodDef testextMethods[] = {
        {"fact", testext_fact, METH_VARARGS},
        {"pgcd", testext_pgcd, METH_VARARGS},
        {NULL, NULL},
    };
 
//###########################################################################
// initialisation du module par Python pendant l'importation
 
PyMODINIT_FUNC inittestext(void) {
    Py_InitModule("testext", testextMethods);
    };

Malgré tout, ce ne sont que des exemples simples et purement numériques. Pour des codes plus complexes, il faudra faire un peu attention, en particulier au points suivants:

  • en cas d'allocation mémoire en C++, il ne faudra pas oublier de supprimer avant de sortir.
  • en cas d'intervention directe sur les variables Python par le C++, attention aux compteurs de référence (il y a des macros pour incrémenter et décrémenter ces compteurs)

Voilà le “setup.py” nécessaire à la compilation:

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Python 2.7
 
from distutils.core import setup, Extension
 
ext_modules = [Extension('testext', 
                         sources=['testext.cpp'],
                         include_dirs=["."],
                         language="c++"
                         )
              ]
 
setup(name='testext', 
      ext_modules=ext_modules
     )

Vous voyez que le choix du “C++” est signalé dans le setup

Compilation

La compilation en C++ est obtenue par (en console et dans le répertoire du setup.py):

python setup.py build

Attention sous Windows: choix du compilateur

Python prendra le compilateur par défaut. Si vous avez Visual Studio de Microsoft (comme moi), c'est lui qui sera utilisé (ça fonctionne très bien!). Mais si vous voulez MinGW au lieu de Visual Studio, il y a 2 choses à faire:

1- créer un fichier texte “setup.cfg” placé au même niveau que setup.py, et contenant:

[build]
compiler = mingw32

2- corriger le fichier C:\Python27\Lib\distutils\cygwinccompiler.py pour retirer toute mention de l'option “-mno-cygwin” si elle génère une erreur.

Moyennant quoi, chez moi, ça marche avec les 2 compilateurs.

Avec Windows, il se crée un module “testext.pyd” dans le répertoire build\lib.win32-2.7, et ce module pourra être directement importé par Python pour être utilisé.

Utilisation du module "testext.pyd"

Voilà un petit code d'essai:

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import division
# Python 2.7
 
from testext import fact, pgcd
 
from timeit import default_timer 
from random import randint
 
n = 1000000
a = randint(10,10000)
b = randint(10,10000)
 
t1 = default_timer()
for c in xrange(0, n):
    pgcd(a, b)
t1 = default_timer()-t1
print t1
 
print fact(10)

Exemple d'affichage:

0.382636144736
3628800

A noter que le temps obtenu est un peu moins bon qu'avec Cython! En fait, ce n'est pas le code C++ lui-même qui fait la différence, mais la “machinerie” que Python fabrique autour de ce code pour permettre l'utilisation par l'interpréteur Python. On peut supposer que des fonctions en C++ plus complexes et plus longues donneront moins d'importance à cette “machinerie” et permettront un plus grand avantage de temps d'exécution.

Amusez-vous bien!

exemple_python_cpp.txt · Dernière modification: 2013/10/18 19:04 de tyrtamos

Outils de la page