W tym samouczku pokazano, jak skonfigurować projekt pybind11 z CMake w celu zawinięcia biblioteki C++ do Python.

Ostateczny wynik będzie następujący:

  • C++ Projekt, który możesz zbudować niezależnie od pybind11.
  • Biblioteka Python wygenerowana w wyniku zawinięcia kodu C++.
  • Obydwa używają CMake.

Kod całego projektu znajdziesz tutaj.

"Źródło obrazu."

Wymagania

Oczywiście zdobądź pybind11:

conda install -c conda-forge pybind11

Utwórz projekt C++

Użyjemy zewnętrznego (bieżącego) katalogu roboczego do zbudowania Pythona i wewnętrznego katalogu o nazwie cpp do zbudowania kodu C++. Najpierw utwórz katalog C++.

mkdir cpp
cd cpp

Następnie zainicjujemy projekt w C++. Dwa sposoby (z wielu innych) to:

  1. Używanie VS Code. Zainstaluj rozszerzenie CMake Tools. Następnie wywołaj polecenie paleta i wybierz CMake: Quick start. Postępuj zgodnie z instrukcjami i wprowadź nazwę — wybrałem automobile. Po wyświetleniu monitu o bibliotekę lub plik wykonywalny wybierz library. Twój katalog powinien teraz wyglądać tak:
cpp/build/
cpp/motorcycle.cpp
cpp/CMakeLists.txt

Oddzielimy pliki źródłowe i nagłówkowe — jest to zawsze dobra praktyka. W katalogu cpp utwórz dwa nowe katalogi:

cd cpp
mkdir include
mkdir src

i przenieś plik źródłowy:

mv motorcycle.cpp src/

W katalogu include chcielibyśmy mieć pojedynczy nagłówek do zaimportowania. W ten sposób moglibyśmy później po prostu #include <automobile>. Możemy to zorganizować w następujący sposób:

cd cpp/include
mkdir automobile_bits
touch automobile

Na koniec utwórzmy plik nagłówkowy w katalogu cpp/include/automobile_bits:

cd cpp/include/automobile_bits
touch motorcycle.hpp

Ostateczna struktura katalogów powinna teraz wyglądać następująco:

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

2. Alternatywnie utwórz ręcznie pliki i katalogi tak, aby ostateczna struktura była następująca:

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

Będziemy musieli edytować bieżący CMakeLists.txt tak, aby mógł znaleźć pliki nagłówkowe i źródłowe. Zmodyfikowałem swój, aby brzmiał następująco:

Nadajmy także plikom motorcycle.hpp i motorcycle.cpp rozsądną zawartość. Dla nagłówka:

i źródło:

Tak! Wiem, że są głupi. Pamiętaj, że wprowadziliśmy przestrzeń nazw vehicles — to zawsze dobry pomysł.

Musimy także, aby plik nagłówkowy znalazł rzeczywistą bibliotekę. Edytuj plik include/automobile, aby przeczytać:

Możemy już zbudować bibliotekę:

  1. Korzystanie z wiersza poleceń:
cd cpp/build
cmake ..
make
make install

2. Używając swojego ulubionego IDE, np. XCode:

cd cpp/build
cmake .. -GXcode

powinien wygenerować automobile.xcodeproject w katalogu build.

Tak czy inaczej, powinieneś pobrać bibliotekę do zbudowania i zainstalowania.

Testowanie biblioteki C++

Zanim przejdziemy do pakowania biblioteki w Python, utwórzmy test dla biblioteki C++ (nie prawdziwy test, po prostu miejsce, w którym możemy się pobawić!).

Utwórz nowy katalog w cpp:

cd cpp
mkdir tests

Tutaj ponownie skonfigurujemy projekt CMake dla naszego testu. Spraw, aby struktura katalogów wyglądała następująco:

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

Edytuj plik test.cpp, aby przeczytać:

i edytuj plik CMakeLists.txt:

Utwórz i uruchom tego złego chłopca, używając XCode jak poprzednio, lub z wiersza poleceń:

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

Zauważ, że plik binarny będzie znajdować się w katalogu bin. Dane wyjściowe powinny być następujące:

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

Konfigurowanie opakowania Pythona

Na koniec przejdźmy do pakowania biblioteki w Pythona. Przenosimy katalog w górę! W katalogu głównym utwórzmy nowy katalog o nazwie python. Będzie przechowywać cały kod kleju:

mkdir python

Potrzebujemy także pliku CMakeLists.txt o zawartości:

Powinieneś być gotowy na zbudowanie swojej biblioteki Python! Próbować:

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

Jak zwykle, możesz także wygenerować kod za pomocą generatora dla swojego ulubionego IDE, np. dodając -GXcode do polecenia cmake. Moje ścieżki były:

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

Pamiętaj, że jeśli jesteś leniwy tak jak ja, możesz spróbować dodać do testowania:

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

w twoim CMakeLists.txt — oczywiście nie jest to dobry trik na produkcję!

Uruchom python (upewnij się, że jest taki sam, jak określono w PYTHON_EXECUTABLE powyżej) i spróbuj:

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

Masz niezły, gruby błąd, ale to jest OK! Nie napisaliśmy jeszcze kodu kleju, ale przynajmniej Twój CMake działa i Python może znaleźć Twoją bibliotekę.

Opakowanie biblioteki w Pythonie

Teraz o właściwej logice zawinięcia kodu C++ w Python. Odbędzie się to w katalogu python. Najpierw utwórz plik, który będzie definiował „funkcję eksportu modułu”, na którą python narzekał w ostatniej części:

touch python/automobile.cpp

Nadaj mu następującą treść:

Następnie zdefiniujemy zadeklarowaną metodę init_motorcycle. Zrobimy to w osobnym pliku:

touch python/motorcycle.cpp

Edytuj, aby przeczytać:

Zawsze uważam, że sam kod jest najlepszym wyjaśnieniem, ale kilka wskazówek:

  • ”Motorcycle” definiuje nazwę klasy w Python — możesz ją zmienić, jeśli chcesz! Zwróć także uwagę na wygląd przestrzeni nazw.
  • .def(py::init<std::string>(), py::arg(“name”)) definiuje konstruktora. py::arg(“name”) pozwala na użycie nazwanych argumentów w Python.
  • .def(“get_name”, py::overload_cast<>( &vehicles::Motorcycle::get_name, py::const_)) otacza metodę get_name. Zwróć uwagę na sposób pakowania deklaracji const.
  • .def(“ride”, py::overload_cast<std::string>( &vehicles::Motorcycle::ride, py::const_), py::arg(“road”)); otacza metodę ride. Argumenty metody są zadeklarowane w py::overload_cast<std::string> (oddzielone przecinkami, jeśli jest ich wiele) i ponownie można je nazwać za pomocą py::arg(“road”). Zwróć także uwagę na średnik na końcu — często zapominany, ale powinien to być prawidłowy kod C++.

Możesz teraz przetestować swoją bibliotekę. Uruchom ponownie make i make install, aby odbudować i zainstalować bibliotekę.

Uruchom python i wypróbuj:

powinien dać taki sam wynik jak poprzednio:

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

Możesz utworzyć inny skrypt testowy z tą zawartością i umieścić go w katalogu tests/test.py.

Wniosek

To wszystko w tym samouczku. Pełny kod znajdziesz tutaj.

Zaletą tej konfiguracji jest to, że możesz spokojnie zbudować swój projekt C++ z katalogu cpp, a na końcu w warstwie zewnętrznej martwić się o zawinięcie go do Python.

Możesz przeczytać o bardziej zaawansowanych funkcjach pybind11 w innym samouczku, który napisałem tutaj.

Dziękuje za przeczytanie!