В этом руководстве показано, как настроить проект pybind11 с CMake для обертывания библиотеки C++ в Python.

Конечный результат будет:

  • C++ проект, который можно построить независимо от pybind11.
  • Библиотека Python, созданная путем упаковки кода C++.
  • Оба используют CMake.

Здесь вы можете найти код для всего проекта.

Источник изображения.

Требования

Очевидно, получаем pybind11:

conda install -c conda-forge pybind11

Создайте проект C ++

Мы будем использовать внешний (текущий) рабочий каталог для сборки Python и внутренний каталог с именем cpp для сборки кода C++. Сначала создайте каталог C++.

mkdir cpp
cd cpp

Затем мы инициализируем проект C ++. Двумя способами (и их гораздо больше) являются:

  1. Используя VS Code. Установите расширение CMake Tools. Затем откройте палитру команд и выберите CMake: Quick start. Следуйте подсказкам и введите имя - я выбрал automobile. Когда будет предложено выбрать библиотеку или исполняемый файл, выберите library. Теперь ваш каталог должен выглядеть так:
cpp/build/
cpp/motorcycle.cpp
cpp/CMakeLists.txt

Мы разделим исходный и заголовочный файлы - это всегда хорошая практика. В каталоге cpp создайте два новых каталога:

cd cpp
mkdir include
mkdir src

и переместите исходный файл:

mv motorcycle.cpp src/

В каталоге include мы хотели бы иметь один заголовок для импорта. Таким образом, позже мы могли бы просто #include <automobile>. Мы можем организовать это следующим образом:

cd cpp/include
mkdir automobile_bits
touch automobile

Наконец, давайте создадим файл заголовка в каталоге cpp/include/automobile_bits:

cd cpp/include/automobile_bits
touch motorcycle.hpp

Окончательная структура каталогов должна теперь выглядеть так:

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

2. В качестве альтернативы, вручную создайте файлы и каталоги, чтобы окончательная структура была следующей:

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

Нам нужно будет отредактировать текущий CMakeLists.txt, чтобы он мог найти заголовочные и исходные файлы. Я отредактировал свой, чтобы он читался следующим образом:

Давайте также добавим файлам motorcycle.hpp и motorcycle.cpp разумное содержание. Для заголовка:

и источник:

Да! Я знаю, что они тупые. Обратите внимание, что мы ввели пространство имен vehicles - это всегда хорошая идея.

Нам также нужно, чтобы файл заголовка находил актуальную библиотеку. Отредактируйте include/automobile файл следующим образом:

Теперь мы уже можем собрать библиотеку:

  1. Используя командную строку:
cd cpp/build
cmake ..
make
make install

2. Используя вашу любимую IDE, например XCode:

cd cpp/build
cmake .. -GXcode

должен сгенерировать automobile.xcodeproject в каталоге build.

В любом случае вы должны получить библиотеку для сборки и установки.

Тестирование библиотеки C ++

Прежде чем мы перейдем к обертке библиотеки в Python, давайте создадим тест для библиотеки C++ (не настоящий тест, просто где-нибудь, чтобы мы могли возиться!).

Создайте новый каталог в cpp:

cd cpp
mkdir tests

Здесь мы снова создадим CMake проект для нашего теста. Сделайте структуру каталогов такой:

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

Отредактируйте файл test.cpp, чтобы он читался:

и отредактируйте CMakeLists.txt файл:

Создайте и запустите этого плохого парня, используя XCode, как раньше, или из командной строки:

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

Обратите внимание, что двоичный файл будет в bindirectory. Результат должен быть:

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

Настройка оболочки Python

Наконец, давайте перейдем к обертке библиотеки на Python. Мы движемся вверх по каталогу! В главном каталоге создадим новый каталог с именем python. Он будет содержать весь код клея:

mkdir python

Также нам понадобится CMakeLists.txt файл с содержимым:

Вы должны быть готовы создать свою Python библиотеку! Пытаться:

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

Как обычно, вы также можете сгенерировать код, используя генератор для вашей любимой IDE, например добавив -GXcode к команде cmake. Мои пути были:

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

Обратите внимание: если вы, как я, ленивы, вы можете попробовать добавить для тестирования:

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

в вашем CMakeLists.txt - явно не удачный трюк для продакшена!

Запустите python (убедитесь, что он такой же, как вы указали в PYTHON_EXECUTABLE выше) и попробуйте:

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

У вас хорошая жирная ошибка, но ничего страшного! Мы еще не написали связующий код, но по крайней мере ваш CMake работает и Python может найти вашу библиотеку.

Оборачивание библиотеки в Python

Теперь о самой логике обертывания кода C++ в Python. Он будет размещен в каталоге python. Сначала создайте файл, который будет определять «функцию экспорта модуля», о которой python говорил в предыдущей части:

touch python/automobile.cpp

Дайте ему следующее содержание:

Далее мы определим объявленный метод init_motorcycle. Сделаем это в отдельном файле:

touch python/motorcycle.cpp

Отредактируйте его так:

Я всегда считаю, что сам код является лучшим объяснением, но несколько указателей:

  • ”Motorcycle” определяет имя класса в Python - вы можете изменить его, если хотите! Обратите внимание также на внешний вид пространства имен.
  • .def(py::init<std::string>(), py::arg(“name”)) определяет конструктор. py::arg(“name”) позволяет использовать именованные аргументы в Python.
  • .def(“get_name”, py::overload_cast<>( &vehicles::Motorcycle::get_name, py::const_)) является оболочкой для метода get_name. Обратите внимание, как завернуто объявление const.
  • .def(“ride”, py::overload_cast<std::string>( &vehicles::Motorcycle::ride, py::const_), py::arg(“road”)); является оболочкой для метода ride. Аргументы метода объявлены в py::overload_cast<std::string> (разделены запятыми, если их несколько), и их можно снова назвать с помощью py::arg(“road”). Также обратите внимание на точку с запятой в конце - часто забывают, но это должен быть правильный C++ код.

Теперь вы можете протестировать свою библиотеку. Запустите make и make install еще раз, чтобы перестроить и установить библиотеку.

Запустите python и попробуйте:

должен дать тот же результат, что и раньше:

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

Вы можете создать другой тестовый сценарий с этим содержимым, расположенный в каталоге tests/test.py.

Заключение

Это все для этого урока. Вы можете найти полный код здесь.

Приятная часть этой настройки заключается в том, что вы можете спокойно собрать свой C++ проект из каталога cpp, а затем, в конце внешнего слоя, беспокоиться о том, чтобы обернуть его в Python.

Вы можете прочитать о дополнительных функциях pybind11 в другом руководстве, которое я написал здесь.

Спасибо за прочтение!