Embeder Python dans du C ou C++ 10


L’implémentation de référence de Python est écrite en C, et son API est exposée et bien documentée. Il est donc possible de créer des objets Python, charger un module Python ou exécuter une fonction Python depuis un code C/C++ et compiler tout ça.

Mettons que j’ai dans un fichier biblio.py :

def yolo(arg):
    return arg.upper() + ' !!'

Je peux écrire un fichier prog.c qui l’utilise :

#include <Python.h>
 
int main () {
    // PyObject est un wrapper Python autour des objets qu'on
    // va échanger enter le C et Python.
    PyObject *retour, *module, *fonction, *arguments;
    char *resultat;
 
    // Initialisation de l'interpréteur. A cause du GIL, on ne peut
    // avoir qu'une instance de celui-ci à la fois.
    Py_Initialize();   
 
    // Import du script. 
    PySys_SetPath("."); // Le dossier en cours n'est pas dans le PYTHON PATH
    module = PyImport_ImportModule("biblio");
 
    // Récupération de la fonction
    fonction = PyObject_GetAttrString(module, "yolo");
 
    // Création d'un PyObject de type string. Py_BuildValue peut créer
    // tous les types de base Python. Voir :
    // https://docs.python.org/2/c-api/arg.html#c.Py_BuildValue
    arguments = Py_BuildValue("(s)", "Leroy Jenkins"); 
 
    // Appel de la fonction.
    retour = PyEval_CallObject(fonction, arguments);
 
    // Conversion du PyObject obtenu en string C
    PyArg_Parse(retour, "s", &resultat);
 
    printf("Resultat: %s\n", resultat);
 
    // On ferme cet interpréteur.
    Py_Finalize(); 
    return 0;
}

Là je mets du C, mais la seule vraie différence avec du C++, c’est qu’on aurait cout au lieu de printf.

Pour compiler tout ça, il faut les headers Python et un compilateur. Sous Ubuntu, c’est un simple :

sudo apt-get install python-dev gcc

Et on a tout nos .h dans /usr/include/python2.7. On gccise :

gcc -I/usr/include/python2.7 prog.c -lpython2.7 -o prog -Wall  && ./prog

Même pas un warning, c’est beau.

./prog
Resultat: LEROY JENKINS !!

C’est un exemple simple, mais comme vous le savez je suis une grosse burne en C, donc je ne pourrai pas porter l’expérience plus loin.

Je ne le recommande pas pour rendre votre programme scriptable en Python. Il vaut mieux permettre à Python d’appeler votre code C dans ce cas. Des outils comme cffi rendent cela beaucoup plus facile et rentable que tout faire tout le taff à la main. D’ailleurs si quelqu’un est chaud pour faire un tuto sur cffi…

Non, c’est plus dans le cas où vous avez un programme C, un programme Python, et votre programme C veut utiliser le programme Python sans avoir à tout réécrire. Ou pour le cas où vous avez décidé d’écrire une grosse partie de votre programme en Python pour profiter de sa productivité, mais que vous ne pouvez pas installer Python sur la machine sur laquelle vous aller installer le programme. Bon, y a PyInstaller et Nuitka pour ça également hein, donc tentez avant de tout embeder comme un bourrin.

Ça reste intéressant de voir les entrailles de CPython et à quel point il joue bien avec les langages bas niveaux.

10 thoughts on “Embeder Python dans du C ou C++

  • Seb

    Cool ce truc ! Est-ce que le code compilé fait appel aux libs python présentes sur le système ? En d’autres termes, est-ce que le prog fonctionne correctement si python n’est pas installé sur la machine ?

    As-tu un lien pour faire la même chose en java ?

  • Gnukos

    Cool !!

    Pourquoi tu retourne 1 dans ta fonction main? C’est 0 pour dire que tout c’est bien passé ;-) et même EXIT_SUCCESS si tu veux le faire en plus mieux bien XD

  • Sam Post author

    @Gnukos : typo.

    @Sabcat: python est portable par défaut. En plus l’interpréteur est déjà installé sur Mac et Linux. Par contre, avec la compile, tu peux rentre ton code indépendant de l’installation manuelle de la VM par l’utilisateur. Mais c’est pas le meilleur moyen de le faire. Comme signalé dans l’article, PyInstaller, Nuitka, ou juste un installeur qui installe la VM, on obtient un meilleur résultat en se faisant moins chier.

  • Raito Bezarius

    En C++, si t’as accès à Boost, tu peux aussi utiliser Boost.Python.

  • Martin

    Bonjour,

    je viens d’essayer de compiler votre code en C++ mais j’obtiens l’erreur suivante :

    g++ -I/usr/include/python2.7 prog.cpp -lpython2.7 -o prog && ./progprog.cpp: In function ‘int main()’:

    prog.cpp:17:22: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]

    PySys_SetPath(“.”); // Le dossier en cours n’est pas dans le PYTHON PATH

    ^

    Resultat : LEROY JENKINS !!

    Comment y remédier ?

    Merci bien :)

  • zizou

    bonjour

    merci beaucoup pour cet article j ai fait quelque teste ca marche tres bien mais j ai un petit problème quand j essaye d intégré une bibliothèque (beautifulsoup)

    j ai mon fichier prog.c:

    include <stdlib.h>

    include <stdio.h>

    include <string.h>

    include “Python.h”

    int main () {

    // PyObject est un wrapper Python autour des objets qu’on

    // va échanger enter le C et Python.

    PyObject *retour, *module, *fonction, *arguments;

    char *resultat;

    char test[]=”lien_1.html”;

    // Initialisation de l’interpréteur. A cause du GIL, on ne peut

    // avoir qu’une instance de celui-ci à la fois.

    Py_Initialize();

    // Import du script. 
    PySys_SetPath("."); // Le dossier en cours n'est pas dans le PYTHON PATH
    module = PyImport_ImportModule("prog");
    
    // Récupération de la fonction
    fonction = PyObject_GetAttrString(module, "yolo");
    
    // Création d'un PyObject de type string. Py_BuildValue peut créer
    // tous les types de base Python. Voir :
    // https://docs.python.org/2/c-api/arg.html#c.Py_BuildValue
    arguments = Py_BuildValue("(s)", test); 
    
    // Appel de la fonction.
    retour = PyEval_CallObject(fonction, arguments);
    
    // Conversion du PyObject obtenu en string C
    PyArg_Parse(retour, "s", &resultat);
    
    printf("Resultat: %s\n", resultat);
    
    // On ferme cet interpréteur.
    Py_Finalize(); 
    return 0;
    

    }

    et mon fichier prog1.py

    !/usr/bin/env python

    -- coding: utf-8 --

    from bs4 import BeautifulSoup

    def yolo(arg):

    fichier = open(arg,”r”)

    soup = BeautifulSoup(fichier,”lxml”)

    fichier.close()

    fichier2 = open(“lien_1.txt”,”w”)

    for p in soup.find_all(‘p’):

    fichier2.write(str(p))

    fichier.close()
    return arg
    

    mais j ai une erreur de segmentation pouvez vous m aider svp

    merci par avance

  • Sam Post author

    Va plutôt poster ça sur indexerror, ça sera plus facile de t’aider là bas.

Leave a comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.