User Tools

Site Tools


python:wrapper

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
python:wrapper [2022/06/25 14:27] francoispython:wrapper [2022/06/28 08:38] (current) luke
Line 4: Line 4:
  
 L'explication s'appuie sur un exemple d'une fonction et d'une classe écrite en C++. Les codes sources sont dans un répertoire : L'explication s'appuie sur un exemple d'une fonction et d'une classe écrite en C++. Les codes sources sont dans un répertoire :
- 
 |<code> |<code>
 drwxr-xr-x 3 4096  build drwxr-xr-x 3 4096  build
Line 13: Line 12:
 -rw-r--r-- 1  228  test.hpp -rw-r--r-- 1  228  test.hpp
 -rwxr-xr-x 1  336  test.py -rwxr-xr-x 1  336  test.py
-</code> | \\ {{ :python:cmakelists.txt |}} \\ \\ {{ :python:pywrapper.cpp |}} \\ {{ :python:test.cpp |}} \\ {{ :python:test.hpp |}} \\ {{ :python:test.py |}} | <code>+</code> | <code>
 répertoire pour créer les exécutables répertoire pour créer les exécutables
 Règles de fabrication des binaires Règles de fabrication des binaires
Line 22: Line 21:
 programme python  programme python 
 </code> | </code> |
 +
 +<WRAP center round download 60%>
 +Ensemble des sources {{ :python:boostpythontest.tgz |boostpythontest.tgz}} (5 fichiers)
 +</WRAP>
  
 ===== Code C++ ===== ===== Code C++ =====
Line 29: Line 32:
   * construction d'un objet   * construction d'un objet
   * surcharge de constructeur   * surcharge de constructeur
-  * accès de variable en lecture/écriture (par valeur et par méthode)+  * accès d'attribut en lecture/écriture (par valeur et par méthode)
   * invocation de méthode   * invocation de méthode
 +
 +L'interface se trouve dans le fichier entête C++ (''headers'')
  
 {{ :python:test.hpp |}} {{ :python:test.hpp |}}
Line 37: Line 42:
  
 using namespace std; using namespace std;
 +</code> 
 +Déclaration d'une fonction 
 +<code c++>
 string getMsg (); string getMsg ();
- +</code> 
 +Déclaration d'une classe 
 +<code c++>
 class Mem { class Mem {
 private: private:
Line 56: Line 64:
 }; };
 </code> </code>
 +
 +Le programme proprement dit (les données initiales et le comportement) se trouve dans un fichier ''cpp''.
  
 {{ :python:test.cpp |}} {{ :python:test.cpp |}}
Line 62: Line 72:
  
 #include "test.hpp" #include "test.hpp"
 +</code> 
 +La trace sera affichée au moment de l'appel dans l'environnement C++. 
 +<code c++>
 string string
 getMsg () { getMsg () {
Line 68: Line 80:
   return "hello, world";   return "hello, world";
 } }
 +</code> 
 +Exemple de constructeurs surchargés. 
 +<code c++>
 Mem::Mem (string msg) Mem::Mem (string msg)
   : Mem (msg, 0) {   : Mem (msg, 0) {
Line 79: Line 93:
 Mem::~Mem () { Mem::~Mem () {
 } }
 +</code> 
 +Les accesseurs pour être utilisés pour simuler l'accès à un attribut python. 
 +<code c++>
 void void
 Mem::set (string msg) { Mem::set (string msg) {
Line 92: Line 108:
  
 ===== Code Python ===== ===== Code Python =====
 +
 +La première ligne permet de sélectionner le bon interpréteur.
  
 {{ :python:test.py |}} {{ :python:test.py |}}
 <code python> <code python>
 #! /usr/bin/python3 #! /usr/bin/python3
 +</code> 
 +Le nom du module correspond au nom de fichier suivi de l'extension ''.so''
 +<code python>
 from libtest import getMsgPy, MemPy from libtest import getMsgPy, MemPy
 +</code> 
 +Appel d'une fonction 
 +<code python>
 print (getMsgPy ()) print (getMsgPy ())
 +</code> 
 +Création d'un objet C++ avec appel de constructeur. 
 +<code python>
 word = MemPy ('A') word = MemPy ('A')
 +</code>
 +Liaison lecture/écriture d'un attribut.
 +<code python>
 word.id = 1; word.id = 1;
 print (f"m:{word.getPy ()} id:{word.idPy}") print (f"m:{word.getPy ()} id:{word.idPy}")
Line 109: Line 136:
 word2 = MemPy ('C', 1) word2 = MemPy ('C', 1)
 print (f"m:{word2.getPy ()} id:{word2.idPy}") print (f"m:{word2.getPy ()} id:{word2.idPy}")
 +</code> 
 +Utilisation des accesseurs. 
 +<code python>
 word2.msgPy = word.msgPy+"2" word2.msgPy = word.msgPy+"2"
 print (word2.msgPy) print (word2.msgPy)
Line 115: Line 144:
  
 ===== Passerelle ===== ===== Passerelle =====
 +
 +La liaison ce fait grâce à la bibliothèque Boost avec un ensemble de déclarations.
  
 {{ :python:pywrapper.cpp |}} {{ :python:pywrapper.cpp |}}
Line 123: Line 154:
  
 using namespace boost::python; using namespace boost::python;
 +</code> 
 +''libtest'' est le nom du fichier sans l'extension ''.so''. Si l'on fait deux bibliothèques (par exemple ''libtest.so'' et ''libtestdebug.so''), il faudra deux déclarations. 
 +<code c++>
 BOOST_PYTHON_MODULE (libtest) { BOOST_PYTHON_MODULE (libtest) {
-  //Py_Initialize (); +</code> 
 +Le nom entre guillemets est l'identifiant utilisé dans le programme python. Il peut être le même qu'en C++, mais ici cela permet de montrer la différence entre le contexte C++ et Python. 
 +L'identifiant ''getMsg'' est directement un pointeur sur la fonction. 
 +<code c++>
   def ("getMsgPy", getMsg);   def ("getMsgPy", getMsg);
 +</code> 
 +La déclaration d'un constructeur est optionnelle. 
 +<code c++>
   class_<Mem> ("MemPy", init<string>())   class_<Mem> ("MemPy", init<string>())
 +</code>
 +On peut même ajouter la déclaration d'autres constructeurs.
 +<code c++>
     .def (init<string, int>())     .def (init<string, int>())
 +</code>
 +Comme l'invocation de la méthode ''.def'' retourne un pointeur sur le même objet, on peut faire une cascade d'appel (il ne faut pas de ";" entre chaque).
 +Un pointeur sur un membre (ici une méthode) de la classe ''Men'' permet la liaison avec le code C++.
 +<code c++>
     .def ("getPy", &Mem::get)     .def ("getPy", &Mem::get)
     .def ("setPy", &Mem::set)     .def ("setPy", &Mem::set)
 +</code>
 +La liaison entre l'identifiant Python et l'homologue C++ permet d'utiliser la variable Python à droite ou à gauche d'un signe égale (on dit ''leftvalue'' lorsque l'on peut affecter une valeur)
 +<code c++>
     .def_readwrite ("idPy", &Mem::id)     .def_readwrite ("idPy", &Mem::id)
 +</code>
 +Ici on a recours à des accesseurs.
 +Il faudra ne pas oublier le ";" pour clore la série de déclaration.
 +<code c++>
     .add_property ("msgPy", &Mem::get, &Mem::set)     .add_property ("msgPy", &Mem::get, &Mem::set)
     ;     ;
 } }
- 
-static bool initWrapper () { 
-  //Py_Initialize (); 
-  // boost::python::numpy::initialize (); 
-  return true; 
-} 
-static bool isWrapperInit (initWrapper ()); 
 </code> </code>
  
 ===== Compilation ===== ===== Compilation =====
 +
 +Pour simplifier la création de la librairie C++ qui sera vu comme un module Python, nous avons utilisé ''cmake''.
 +
 {{ :python:cmakelists.txt |}} {{ :python:cmakelists.txt |}}
 <code cmake> <code cmake>
Line 154: Line 201:
 set (CMAKE_CXX_STANDARD 11) set (CMAKE_CXX_STANDARD 11)
 set (CXX_STANDARD_REQUIRED ON) set (CXX_STANDARD_REQUIRED ON)
-set (BUILD_SHARED_LIBS ON)+</code>
  
 +<WRAP center round important 60%>
 +Il est important de produire une bibliothèque dynamique (''SHARED'') pour qu'elle soit chargée à la volée dans l'environnement Python.
 +</WRAP>
 +<code cmake>
 +set (BUILD_SHARED_LIBS ON)
 +</code>
 +Nous allons dépendre d'autres bibliothèques.
 +<code cmake>
 find_package (PythonLibs 3 REQUIRED) find_package (PythonLibs 3 REQUIRED)
 find_package (Boost REQUIRED python3 system chrono) find_package (Boost REQUIRED python3 system chrono)
 +</code> 
 +Nous créons une liste de sources pour constituer notre bibliothèque. 
 +<code cmake>
 list (APPEND libTestSrc list (APPEND libTestSrc
   "test.cpp"   "test.cpp"
   "pywrapper.cpp"   "pywrapper.cpp"
   )   )
 +</code> 
 +Répertoire où l'on trouve les entêtes nécessaires à la compilation. 
 +<code cmake>
 include_directories ( include_directories (
   PUBLIC   PUBLIC
Line 170: Line 229:
   ${PYTHON_INCLUDE_DIRS}   ${PYTHON_INCLUDE_DIRS}
   )   )
 +</code> 
 +Compilation de tous les modules C++ et préparation de l'archive. 
 +<code cmake>
 add_library (test SHARED add_library (test SHARED
   ${libTestSrc}   ${libTestSrc}
   )   )
 +</code> 
 +<WRAP center round important 60%> 
 +Cette directive est indispensable. Sans quoi, on aura l'illusion que tout est fait, mais au moment du chargement du module par python, il ne sera pas trouver les dépendance. Il faudrait alors charger dans l'environnement python les autres bibliothèques (Boost, pythonLib). 
 +</WRAP> 
 +<code cmake>
 target_link_libraries (test target_link_libraries (test
   ${PYTHON_LIBRARIES}   ${PYTHON_LIBRARIES}
Line 181: Line 246:
 </code> </code>
  
 +Installation du package ''libboost-python-dev''.
 +<code bash>
 +sudo apt-get install -y libboost-python-dev
 +</code>
 +
 +Nous allons créer les binaires dans un sous-répertoire ''build''.
 <code bash> <code bash>
 mkdir build/ mkdir build/
Line 186: Line 257:
 cmake .. cmake ..
 make -j $(nproc) make -j $(nproc)
 +</code>
 +
 +<WRAP center round important 60%>
 +L'interpréteur va rechercher les modules python à importer (des bibliothèques dynamiques) en fonction d'un chemin qui peut contenir des répertoires relatifs ou absolus.
 +</WRAP>
 +Nous avons indiqué "." car nous exécutons le programme dans le sous-répertoire. Si nous avions exécuté depuis l'endroit où se trouve le programme Python, il aurait fallu indiquer "./build/".
 +<code bash>
 export PYTHONPATH=".:/usr/local/lib/python3/" export PYTHONPATH=".:/usr/local/lib/python3/"
 +</code>
 +
 +Nous pouvons explicitement rendre le programme python exécutable et l'invoquer.
 +<code bash>
 +chmod a+x ../test.py
 ../test.py  ../test.py 
 +</code>
 +Voici le résultat en commençant par la trace réalisée dans l'environnement C++.
 +<code bash>
 coucou coucou
 hello, world hello, world
python/wrapper.1656167227.txt.gz · Last modified: 2022/06/25 14:27 by francois