Acest tutorial arată cum să configurați un proiect pybind11 cu CMake pentru a include o bibliotecă C++ în Python.

Rezultatul final va fi:

  • Un proiect C++ pe care îl puteți construi independent de pybind11.
  • O bibliotecă Python generată din împachetarea codului C++.
  • Ambele folosind CMake.

„Codul pentru proiectul complet îl găsiți aici.”

„Sursa imaginii.”

Cerințe

Evident, obțineți pybind11:

conda install -c conda-forge pybind11

Creați un proiect C++

Vom folosi directorul de lucru exterior (actual) pentru a construi python și un director interior numit cpp pentru a construi codul C++. Mai întâi faceți un director C++.

mkdir cpp
cd cpp

În continuare, vom inițializa un proiect C++. Două moduri (din multe altele) sunt:

  1. Folosind VS Code. Instalați extensia CMake Tools. Apoi, deschideți paleta de comenzi și selectați CMake: Quick start. Urmați instrucțiunile și introduceți un nume — am ales automobile. Când vi se solicită bibliotecă sau executabil, alegeți library. Directorul dvs. ar trebui să arate acum astfel:
cpp/build/
cpp/motorcycle.cpp
cpp/CMakeLists.txt

Vom separa fișierele sursă și antet - aceasta este întotdeauna o practică bună. În directorul cpp, creați două directoare noi:

cd cpp
mkdir include
mkdir src

și mutați fișierul sursă:

mv motorcycle.cpp src/

În directorul include, am dori să avem un singur antet de importat. În acest fel, mai târziu am putea pur și simplu #include <automobile>. O putem organiza astfel:

cd cpp/include
mkdir automobile_bits
touch automobile

În cele din urmă, să creăm un fișier antet în directorul cpp/include/automobile_bits:

cd cpp/include/automobile_bits
touch motorcycle.hpp

Structura finală a directoarelor ar trebui să arate acum astfel:

cpp/build
cpp/CMakeLists.txt
cpp/include/automobile
cpp/include/automobile_bits/motorcycle.hpp
cpp/src/motorcycle.cpp

2. Alternativ, creați manual fișierele și directoarele, astfel încât structura finală să fie:

cpp/build
cpp/CMakeLists.txt
cpp/include/automobile
cpp/include/automobile_bits/motorcycle.hpp
cpp/src/motorcycle.cpp

Va trebui să edităm CMakeLists.txt curent astfel încât să poată găsi antetul și fișierele sursă. L-am editat pe al meu pentru a citi astfel:

Să dăm și fișierelor motorcycle.hpp și motorcycle.cpp un conținut rezonabil. Pentru antet:

si sursa:

Da! Stiu ca sunt prosti. Rețineți că am introdus un spațiu de nume vehicles - aceasta este întotdeauna o idee bună.

De asemenea, trebuie ca fișierul antet să găsească biblioteca reală. Editați fișierul include/automobile pentru a citi:

Acum putem construi deja biblioteca:

  1. Folosind linia de comandă:
cd cpp/build
cmake ..
make
make install

2. Folosind IDE-ul tău preferat, de ex. XCode:

cd cpp/build
cmake .. -GXcode

ar trebui să genereze automobile.xcodeproject în directorul build.

În orice caz, ar trebui să construiți și să instalați biblioteca.

Testarea bibliotecii C++

Înainte de a trece la împachetarea bibliotecii în Python, haideți să creăm un test pentru biblioteca C++ (nu un test real, doar undeva pentru a ne încurca!).

Creați un director nou în cpp:

cd cpp
mkdir tests

Aici vom configura din nou un proiect CMake pentru testul nostru. Faceți ca structura directorului să arate după cum urmează:

cpp/tests/CMakeLists.txt
cpp/tests/src/test.cpp

Editați fișierul test.cpp pentru a citi:

și editați fișierul CMakeLists.txt:

Faceți și rulați acel băiat rău folosind XCode ca înainte sau din linia de comandă:

mkdir build
cd build
cmake ..
make
cd ../bin
./test

Rețineți că binarul va fi în directorul bin. Ieșirea ar trebui să fie:

Made a motorcycle called: Yamaha
Zoom Zoom on road: mullholland

Configurarea wrapper-ului Python

În cele din urmă, să trecem la împachetarea bibliotecii într-un Python. Mutăm într-un director! În directorul principal, să facem un nou director numit python. Va deține tot codul de lipici:

mkdir python

Avem nevoie și de un fișier CMakeLists.txt, cu conținut:

Ar trebui să fiți gata să vă construiți biblioteca Python! Încerca:

mkdir build
cd build
cmake .. -DPYTHON_LIBRARY_DIR=”/path/to/site-packages” -DPYTHON_EXECUTABLE=”/path/to/executable/python3"
make
make install

Ca de obicei, puteți genera cod folosind un generator pentru IDE-ul dvs. preferat, de ex. prin adăugarea -GXcode la comanda cmake. Drumurile mele au fost:

DPYTHON_LIBRARY_DIR=”/Users/USERNAME/opt/anaconda3/lib/python3.7/site-packages”
DPYTHON_EXECUTABLE=”/Users/USERNAME/opt/anaconda3/bin/python3"

Rețineți că, dacă sunteți leneș ca mine, puteți încerca să adăugați pentru testare:

set(PYTHON_LIBRARY_DIR “/Users/USERNAME/opt/anaconda3/lib/python3.7/site-packages”)
set(PYTHON_EXECUTABLE “/Users/USERNAME/opt/anaconda3/bin/python3”)

în CMakeLists.txt dvs. — evident că nu este un truc bun pentru producție!

Porniți python (asigurați-vă că este același cu cel specificat în PYTHON_EXECUTABLE de mai sus) și încercați:

>>> import automobile
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
ImportError: dynamic module does not define module export function (PyInit_automobile)

Ai o eroare groasă, dar e în regulă! Nu am scris încă codul lipici, dar cel puțin CMake funcționează și Python vă poate găsi biblioteca.

Încheierea bibliotecii în Python

Acum, pentru logica reală a împachetării codului C++ în Python. Va avea loc în directorul python. Mai întâi creați un fișier care va defini „funcția de export de modul” despre care python se plângea în ultima parte:

touch python/automobile.cpp

Dă-i următorul conținut:

În continuare, vom defini metoda init_motorcycle care a fost declarată. Vom face acest lucru într-un fișier separat:

touch python/motorcycle.cpp

Editează-l pentru a citi:

Întotdeauna cred că codul în sine este cea mai bună explicație, dar câteva indicații:

  • ”Motorcycle” definește numele clasei în Python — îl puteți schimba dacă doriți! Observați și aspectul spațiului de nume.
  • .def(py::init<std::string>(), py::arg(“name”)) definește constructorul. py::arg(“name”) vă permite să utilizați argumente numite în Python.
  • .def(“get_name”, py::overload_cast<>( &vehicles::Motorcycle::get_name, py::const_)) include metoda get_name. Observați cum este împachetat declarația const.
  • .def(“ride”, py::overload_cast<std::string>( &vehicles::Motorcycle::ride, py::const_), py::arg(“road”)); include metoda ride. Argumentele metodei sunt declarate în py::overload_cast<std::string> (separate prin virgule dacă sunt multiple) și pot fi denumite din nou folosind py::arg(“road”). De asemenea, notați punctul și virgulă de la sfârșit - adesea uitat, dar acesta ar trebui să fie codul C++ adecvat.

Acum vă puteți testa biblioteca. Rulați din nou make și make install pentru a reconstrui și a instala biblioteca.

Porniți python și încercați:

ar trebui să dea aceeași ieșire ca înainte:

Made a motorcycle called: Yamaha
Zoom Zoom on road: mullholland

Ai putea face un alt script de testare cu acel conținut, situat într-un director tests/test.py.

Concluzie

Asta e pentru acest tutorial. „Codul complet îl găsiți aici.”

Partea plăcută a acestei configurații este că vă puteți construi proiectul C++ în liniște din directorul cpp, iar apoi, la sfârșit, în stratul exterior vă faceți griji că îl includeți în Python.

Puteți citi despre mai multe funcții avansate pybind11 într-un alt tutorial pe care l-am scris aici.

Multumesc pentru lectura!