1. Intégrer Python dans une autre application
*********************************************

Les chapitres précédents couvraient l'extension de Python,
c'est-à-dire, comment enrichir une fonctionnalité de Python en y
attachant une bibliothèque de fonctions C. C'est aussi possible dans
l'autre sens : enrichir vos applications C/C++ en y intégrant Python.
Intégrer Python vous permet d'implémenter certaines fonctionnalités de
vos applications en Python plutôt qu'en C ou C++. C'est utile dans de
nombreux cas, un exemple serait de permettre aux utilisateurs
d'adapter une application à leurs besoins en y écrivant des scripts
Python. Vous pouvez aussi l'utiliser vous même si certaines
fonctionnalités peuvent être rédigées plus facilement en Python.

Intégrer et étendre Python sont des tâches presque identiques. La
différence est qu'en étendant Python, le programme principal reste
l'interpréteur Python, alors qu'en intégrant Python le programme
principal peut ne rien à voir avec Python. C'est simplement quelques
parties du programme qui appellent l'interpréteur Python pour exécuter
un peu de code Python.

En intégrant Python, vous fournissez le programme principal. L'une de
ses tâches sera d'initialiser l'interpréteur. Au minimum vous devrez
appeler "Py_Initialize()". Il est possible, avec quelques appels
supplémentaires, de passer des options à Python. Ensuite vous pourrez
appeler l'interpréteur depuis n'importe quelle partie de votre
programme.

Il existe différents moyens d'appeler l'interpréteur : vous pouvez
donner une chaîne contenant des instructions Python à
"PyRun_SimpleString()", ou vous pouvez donner un pointeur de fichier
*stdio* et un nom de fichier (juste pour nommer les messages d'erreur)
à "PyRunSimpleFile()". Vous pouvez aussi appeler les API de bas niveau
décrites dans les chapitres précédents pour construire et utiliser des
objets Python.

Voir aussi:

  Manuel de référence de l'API Python/C
     Les détails sur l'interface entre Python et le C sont donnés dans
     ce manuel. Pléthore informations s'y trouvent.


1.1. Intégration de très haut niveau
====================================

La manière la plus simple d'intégrer Python est d'utiliser une
interface de très haut niveau. Cette interface a pour but d'exécuter
un script Python sans avoir à interagir avec directement. C'est utile,
par exemple, pour effectuer une opération sur un fichier.

   #define PY_SSIZE_T_CLEAN
   #include <Python.h>

   int
   main(int argc, char *argv[])
   {
       wchar_t *program = Py_DecodeLocale(argv[0], NULL);
       if (program == NULL) {
           fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
           exit(1);
       }
       Py_SetProgramName(program);  /* optional but recommended */
       Py_Initialize();
       PyRun_SimpleString("from time import time,ctime\n"
                          "print('Today is', ctime(time()))\n");
       if (Py_FinalizeEx() < 0) {
           exit(120);
       }
       PyMem_RawFree(program);
       return 0;
   }

C'est la fonction "Py_SetProgramName()" qui devrait être appelée en
premier, avant "Py_Initialize()", afin d'informer l'interpréteur des
chemins vers ses bibliothèques. Ensuite l'interpréteur est initialisé
par "Py_Initialize()", suivi de l'exécution de Python codé en dur
affichant la date et l'heure, puis, l'appel à "Py_FinalizeEx()" éteint
l'interpréteur, engendrant ainsi la fin du programme. Dans un vrai
programme, vous pourriez vouloir lire le script Python depuis une
autre source, peut être depuis un éditeur de texte, un fichier, ou une
base de donnée. Récupérer du code Python depuis un fichier se fait via
"PyRun_SimplFile()", qui vous économise le travail d'allouer de la
mémoire et de charger le contenu du fichier.


1.2. Au-delà de l'intégration de haut niveau : survol
=====================================================

L'interface de haut niveau vous permet d'exécuter n'importe quel
morceau de code Python depuis votre application, mais échanger des
données est quelque peu alambiqué. Si c'est ce dont vous avez besoin,
vous devez utiliser des appels de niveau plus bas. Il vous en coûtera
plus de lignes de C à écrire, mais vous pourrez presque tout faire.

Il est à souligner qu'étendre ou intégrer Python revient à la louche
au même, en dépit de la différence d'intention. La plupart des sujets
parcourus dans les chapitres précédents sont toujours valides. Pour le
prouver, regardez ce qu'un code d'extension de Python vers C fait
réellement :

1. Convertir des valeurs de Python vers le C,

2. Appeler une fonction C en utilisant les valeurs converties, et

3. Convertir les résultats de l'appel à la fonction C pour Python.

Lors de l'intégration de Python, le code de l'interface fait :

1. Convertir les valeurs depuis le C vers Python,

2. Effectuer un appel de fonction de l'interface Python en utilisant
   les valeurs converties, et

3. Convertir les valeurs de l'appel Python pour le C.

Tel que vous le voyez, les conversions sont simplement inversées pour
s'adapter aux différentes directions de transfert inter-langage. La
seule différence est la fonction que vous appelez entre les deux
conversions de données. Lors de l'extension, vous appelez une fonction
C, lors de l'intégration vous appelez une fonction Python.

Ce chapitre ne couvrira pas la conversion des données de Python vers
le C ni l'inverse. Aussi, un usage correct des références, ainsi que
savoir gérer les erreurs sont considérés acquis. Ces aspects étant
identiques à l'extension de l'interpréteur, vous pouvez vous référer
aux chapitres précédents.


1.3. Intégration pure
=====================

L'objectif du premier programme est d'exécuter une fonction dans un
script Python. Comme dans la section à propos des interfaces de haut
niveau, l'interpréteur n'interagit pas directement avec l'application
(mais le fera dans la section suivante).

Le code pour appeler une fonction définie dans un script Python est :

   #define PY_SSIZE_T_CLEAN
   #include <Python.h>

   int
   main(int argc, char *argv[])
   {
       PyObject *pName, *pModule, *pFunc;
       PyObject *pArgs, *pValue;
       int i;

       if (argc < 3) {
           fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
           return 1;
       }

       Py_Initialize();
       pName = PyUnicode_DecodeFSDefault(argv[1]);
       /* Error checking of pName left out */

       pModule = PyImport_Import(pName);
       Py_DECREF(pName);

       if (pModule != NULL) {
           pFunc = PyObject_GetAttrString(pModule, argv[2]);
           /* pFunc is a new reference */

           if (pFunc && PyCallable_Check(pFunc)) {
               pArgs = PyTuple_New(argc - 3);
               for (i = 0; i < argc - 3; ++i) {
                   pValue = PyLong_FromLong(atoi(argv[i + 3]));
                   if (!pValue) {
                       Py_DECREF(pArgs);
                       Py_DECREF(pModule);
                       fprintf(stderr, "Cannot convert argument\n");
                       return 1;
                   }
                   /* pValue reference stolen here: */
                   PyTuple_SetItem(pArgs, i, pValue);
               }
               pValue = PyObject_CallObject(pFunc, pArgs);
               Py_DECREF(pArgs);
               if (pValue != NULL) {
                   printf("Result of call: %ld\n", PyLong_AsLong(pValue));
                   Py_DECREF(pValue);
               }
               else {
                   Py_DECREF(pFunc);
                   Py_DECREF(pModule);
                   PyErr_Print();
                   fprintf(stderr,"Call failed\n");
                   return 1;
               }
           }
           else {
               if (PyErr_Occurred())
                   PyErr_Print();
               fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
           }
           Py_XDECREF(pFunc);
           Py_DECREF(pModule);
       }
       else {
           PyErr_Print();
           fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
           return 1;
       }
       if (Py_FinalizeEx() < 0) {
           return 120;
       }
       return 0;
   }

Ce code charge un script Python en utilisant "argv[1]", et appelle une
fonction dont le nom est dans "argv[2]". Ses arguments entiers sont
les autres valeurs de "argv". Si vous compilez et liez ce programme
(appelons l'exécutable **call**), et l'appelez pour exécuter un script
Python, tel que :

   def multiply(a,b):
       print("Will compute", a, "times", b)
       c = 0
       for i in range(0, a):
           c = c + b
       return c

alors, le résultat sera :

   $ call multiply multiply 3 2
   Will compute 3 times 2
   Result of call: 6

Bien que le programme soit plutôt gros pour ses fonctionnalités, la
plupart du code n'est que conversion de données entre Python et C,
aussi que pour rapporter les erreurs. La partie intéressante, qui
concerne l'intégration de Python débute par :

   Py_Initialize();
   pName = PyUnicode_DecodeFSDefault(argv[1]);
   /* Error checking of pName left out */
   pModule = PyImport_Import(pName);

Après avoir initialisé l'interpréteur, le script est chargé en
utilisant "PyImport_Import()". Cette fonction prend une chaîne Python
pour argument, elle-même construite en utilisant la fonction de
conversion "PyUnicode_FromString()".

   pFunc = PyObject_GetAttrString(pModule, argv[2]);
   /* pFunc is a new reference */

   if (pFunc && PyCallable_Check(pFunc)) {
       ...
   }
   Py_XDECREF(pFunc);

Une fois le script chargé, le nom recherché est obtenu en utilisant
"PyObject_GetAttrString()". Si le nom existe, et que l'objet récupéré
peut être appelé, vous pouvez présumer sans risque que c'est une
fonction. Le programme continue, classiquement, par la construction de
*n*-uplet d'arguments. L'appel à la fonction Python est alors effectué
avec :

   pValue = PyObject_CallObject(pFunc, pArgs);

Après l'exécution de la fonction, "pValue" est soit "NULL", soit une
référence sur la valeur donnée par la fonction. Assurez-vous de
libérer la référence après avoir utilisé la valeur.


1.4. Étendre un Python intégré
==============================

Jusqu'à présent, l'interpréteur Python intégré n'avait pas accès aux
fonctionnalités de l'application elle-même. L'API Python le permet en
étendant l'interpréteur intégré. Autrement dit, l'interpréteur intégré
est étendu avec des fonctions fournies par l'application. Bien que
cela puisse sembler complexe, ce n'est pas si dur. Il suffit d'oublier
que l'application démarre l'interpréteur Python, au lieu de cela,
voyez l'application comme un ensemble de fonctions, et rédigez un peu
de code pour exposer ces fonctions à Python, tout comme vous écririez
une extension Python normale. Par exemple :

   static int numargs=0;

   /* Return the number of arguments of the application command line */
   static PyObject*
   emb_numargs(PyObject *self, PyObject *args)
   {
       if(!PyArg_ParseTuple(args, ":numargs"))
           return NULL;
       return PyLong_FromLong(numargs);
   }

   static PyMethodDef EmbMethods[] = {
       {"numargs", emb_numargs, METH_VARARGS,
        "Return the number of arguments received by the process."},
       {NULL, NULL, 0, NULL}
   };

   static PyModuleDef EmbModule = {
       PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
       NULL, NULL, NULL, NULL
   };

   static PyObject*
   PyInit_emb(void)
   {
       return PyModule_Create(&EmbModule);
   }

Insérez le code ci-dessus juste avant la fonction "main()". Ajoutez
aussi les deux instructions suivantes avant l'appel à
"Py_Initialize()" :

   numargs = argc;
   PyImport_AppendInittab("emb", &PyInit_emb);

These two lines initialize the "numargs" variable, and make the
"emb.numargs()" function accessible to the embedded Python
interpreter. With these extensions, the Python script can do things
like

   import emb
   print("Number of arguments", emb.numargs())

Dans un cas réel, les méthodes exposeraient une API de l'application à
Python.


1.5. Intégrer Python dans du C++
================================

Il est aussi possible d'intégrer Python dans un programme en C++, la
manière exacte dont cela se fait dépend de détails du système C++
utilisé. En général vous écrirez le programme principal en C++,
utiliserez un compilateur C++ pour compiler et lier votre programme.
Il n'y a pas besoin de recompiler Python en utilisant C++.


1.6. Compiler et Lier en environnement Unix ou similaire
========================================================

Ce n'est pas évident de trouver les bonnes options à passer au
compilateur (et *linker*) pour intégrer l'interpréteur Python dans une
application, Python ayant besoin de charger des extensions sous forme
de bibliothèques dynamiques en C (des ".so") pour se lier avec.

Pour trouver les bonnes options de compilateur et *linker*, vous
pouvez exécuter le script "python*X.Y*-config" généré durant
l'installation (un script "python3-config" peut aussi être
disponible). Ce script a quelques options, celles-ci vous seront
utiles :

* "pythonX.Y-config --cflags" vous donnera les options recommandées
  pour compiler :

     $ /opt/bin/python3.11-config --cflags
     -I/opt/include/python3.11 -I/opt/include/python3.11 -Wsign-compare  -DNDEBUG -g -fwrapv -O3 -Wall

* "pythonX.Y-config --ldflags --embed" vous donnera les drapeaux
  recommandés lors de l'édition de lien :

     $ /opt/bin/python3.11-config --ldflags --embed
     -L/opt/lib/python3.11/config-3.11-x86_64-linux-gnu -L/opt/lib -lpython3.11 -lpthread -ldl  -lutil -lm

Note:

  Pour éviter la confusion entre différentes installations de Python,
  (et plus spécialement entre celle de votre système et votre version
  compilée), il est recommandé d'utiliser un chemin absolu vers
  "python*X.Y*-config", comme dans l'exemple précédent.

Si cette procédure ne fonctionne pas pour vous (il n'est pas garanti
qu'elle fonctionne pour toutes les plateformes Unix, mais nous
traiteront volontiers les rapports de bugs), vous devrez lire la
documentation de votre système sur la liaison dynamique (*dynamic
linking*) et / ou examiner le "Makefile" de Python (utilisez
"sysconfig.get_makefile_filename()" pour trouver son emplacement) et
les options de compilation. Dans ce cas, le module "sysconfig" est un
outil utile pour extraire automatiquement les valeurs de configuration
que vous voudrez combiner ensemble. Par exemple :

   >>> import sysconfig
   >>> sysconfig.get_config_var('LIBS')
   '-lpthread -ldl  -lutil'
   >>> sysconfig.get_config_var('LINKFORSHARED')
   '-Xlinker -export-dynamic'
