pybind11: Создание Python-привязок для C++ кода
pybind11 — это легковесная, исключительно заголовочная библиотека для языка C++, которая позволяет создавать высокопроизводительные привязки (bindings) между кодом на C++ и Python. Она является современной и более удобной альтернативой библиотеке Boost.Python. Основная идея заключается в том, что вы пишете небольшой слой "склеивающего" кода на C++, используя макросы и шаблоны pybind11, который компилируется в динамическую библиотеку (файл .so или .pyd). Затем этот файл можно импортировать в Python как обычный модуль.
Зачем это нужно? В реальных проектах часто возникает необходимость объединить высокую производительность и низкоуровневый контроль C++ с гибкостью и богатой экосистемой Python. Вы можете написать критически важный для скорости код (численные алгоритмы, обработка изображений, работа с железом) на C++, а затем использовать его из Python, сохраняя при этом удобство прототипирования и анализа данных. pybind11 делает этот процесс максимально простым и интуитивным, автоматически обрабатывая преобразование типов, управление памятью и вызов исключений.
Ключевые преимущества pybind11: минимальные накладные расходы (overhead) при вызове функций, полная поддержка современного C++ (C++11 и новее), автоматическое преобразование стандартных контейнеров (std::vector, std::map) в списки и словари Python, а также простая и выразительная система регистрации классов, методов и свойств. Библиотека активно развивается и используется в таких крупных проектах, как PyTorch, TensorFlow и Open3D.
Установка
pybind11 — это заголовочная библиотека, поэтому её установка сводится к копированию заголовочных файлов в ваш проект. Самый простой и рекомендуемый способ — использовать менеджер пакетов pip, который установит библиотеку в систему и предоставит утилиту pybind11-config для получения путей компиляции:
pip install pybind11
После установки вы можете проверить, что всё работает, выполнив команду:
pybind11-config --includes
Она выведет флаги компилятора, необходимые для включения заголовочных файлов pybind11. Для компиляции модуля вам также понадобится компилятор C++ (g++, clang, MSVC) и Python-заголовки (обычно устанавливаются вместе с Python). Пример компиляции с использованием CMake или напрямую через g++:
c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
Быстрый старт: минимальный рабочий пример
Создайте файл example.cpp со следующим содержимым:
#include <pybind11/pybind11.h>
namespace py = pybind11;
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin";
m.def("add", &add, "A function that adds two numbers");
}
Скомпилируйте его (замените python3 на ваш интерпретатор):
c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix)
Теперь вы можете использовать модуль из Python:
import example
result = example.add(3, 4)
print(result) # Выведет: 7
print(example.__doc__) # Выведет: pybind11 example plugin
Основные методы и классы pybind11
Ниже приведены ключевые функции и классы библиотеки, которые используются для создания привязок.
1. PYBIND11_MODULE(name, variable)
Сигнатура: PYBIND11_MODULE(name, variable) (макрос)
Что делает: Определяет точку входа для модуля Python. Создает функцию инициализации, которая автоматически вызывается при импорте модуля. name — это имя модуля (без кавычек), variable — имя переменной типа py::module_, которую вы будете использовать для регистрации функций и классов.
Пример:
PYBIND11_MODULE(mymodule, m) {
m.doc() = "My awesome module";
}
2. module_::def(name, func, doc_string)
Сигнатура: py::module_ &def(const char *name, Func &&f, const char *doc = "")
Что делает: Регистрирует функцию C++ как функцию Python в модуле. Автоматически обрабатывает аргументы и возвращаемые значения, преобразуя их в типы Python.
Пример:
std::string greet(const std::string &name) {
return "Hello, " + name + "!";
}
PYBIND11_MODULE(example, m) {
m.def("greet", &greet, "Greets a person by name");
}
3. class_<T>(module, name, doc_string)
Сигнатура: py::class_<T>(py::handle scope, const char *name)
Что делает: Регистрирует класс C++ как класс Python. Позволяет добавлять методы, свойства, статические методы и определять конструкторы. Возвращает объект, который можно дальше настраивать.
Пример:
struct Pet {
Pet(const std::string &name) : name(name) {}
void setName(const std::string &name_) { name = name_; }
const std::string &getName() const { return name; }
std::string name;
};
PYBIND11_MODULE(example, m) {
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &>())
.def("set_name", &Pet::setName)
.def("get_name", &Pet::getName)
.def_readwrite("name", &Pet::name);
}
4. class_::def_readwrite(name, member) и def_readonly(name, member)
Сигнатура: class_ &def_readwrite(const char *name, T Class::*pm)
Что делает: Автоматически создает Python-свойство (property) для чтения и записи (def_readwrite) или только для чтения (def_readonly) для публичного поля-члена класса C++.
Пример: (см. пример выше с .def_readwrite("name", &Pet::name))
5. class_::def_property(name, getter, setter)
Сигнатура: class_ &def_property(const char *name, Getter &&get, Setter &&set)
Что делает: Создает свойство Python с геттером и сеттером. Позволяет контролировать доступ к данным, выполнять валидацию или логику при чтении/записи.
Пример:
py::class_<Pet>(m, "Pet")
.def(py::init<const std::string &>())
.def_property("name", &Pet::getName, &Pet::setName);
6. class_::def_static(name, func)
Сигнатура: class_ &def_static(const char *name, Func &&f)
Что делает: Регистрирует статический метод класса C++ как статический метод класса Python.
Пример:
struct Calculator {
static int multiply(int a, int b) { return a * b; }
};
PYBIND11_MODULE(example, m) {
py::class_<Calculator>(m, "Calculator")
.def_static("multiply", &Calculator::multiply);
}
# В Python: Calculator.multiply(3, 4)
7. enum_<T>(module, name)
Сигнатура: py::enum_<T>(py::handle scope, const char *name)
Что делает: Регистрирует перечисление C++ (enum или enum class) как перечисление Python. Значения автоматически становятся атрибутами класса перечисления.
Пример:
enum class Color { RED, GREEN, BLUE };
PYBIND11_MODULE(example, m) {
py::enum_<Color>(m, "Color")
.value("RED", Color::RED)
.value("GREEN", Color::GREEN)
.value("BLUE", Color::BLUE)
.export_values(); // Делает RED, GREEN, BLUE доступными в пространстве имен модуля
}
8. module_::attr(name, value)
Сигнатура: module_ &attr(const char *name, object &&value)
Что делает: Устанавливает произвольный атрибут модуля. Может быть числом, строкой, списком, словарем или любым другим объектом Python. Полезно для задания констант или версий.
Пример:
PYBIND11_MODULE(example, m) {
m.attr("PI") = 3.14159;
m.attr("VERSION") = "1.0.0";
}
# В Python: print(example.PI)
9. object::cast<T>()
Сигнатура: template <typename T> T cast() const
Что делает: Преобразует объект pybind11 (например, аргумент функции, полученный как py::object) обратно в тип C++. Полезно, когда вы хотите принять "родной" Python-объект и преобразовать его вручную.
Пример:
void process_list(py::list lst) {
for (auto item : lst) {
int value = item.cast<int>();
// Работаем с value
}
}
PYBIND11_MODULE(example, m) {
m.def("process_list", &process_list);
}
10. stl.h — Автоматическое преобразование контейнеров
Сигнатура: Не требуется явного вызова. Достаточно включить заголовок #include <pybind11/stl.h>.
Что делает: Включает автоматическое преобразование между стандартными контейнерами C++ (std::vector, std::map, std::set, std::pair) и их аналогами в Python (списки, словари, множества, кортежи). Это избавляет от необходимости писать код преобразования вручную.
Пример:
#include <pybind11/stl.h>
#include <vector>
#include <string>
std::vector<std::string> get_fruits() {
return {"apple", "banana", "cherry"};
}
PYBIND11_MODULE(example, m) {
m.def("get_fruits", &get_fruits);
}
# В Python: fruits = example.get_fruits() # fruits — это список ['apple', 'banana', 'cherry']
Реальный практический пример: Обработка изображений
Рассмотрим пример создания модуля для быстрой обработки изображений. Допустим, у нас есть класс C++, который преобразует цветное изображение в оттенки серого, используя эффективные вычисления.
// image_processor.cpp
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#include <vector>
#include <cstdint>
namespace py = pybind11;
class ImageProcessor {
public:
// Принимаем изображение как numpy массив (H, W, 3) типа uint8
py::array_t<uint8_t> to_grayscale(py::array_t<uint8_t, py::array::c_style> input) {
auto buf = input.request();
if (buf.ndim != 3 || buf.shape[2] != 3) {
throw std::runtime_error("Input must be a 3D array with 3 channels (RGB)");
}
size_t height = buf.shape[0];
size_t width = buf.shape[1];
auto *ptr = static_cast<uint8_t*>(buf.ptr);
// Создаем выходной массив (H, W) типа uint8
auto result = py::array_t<uint8_t>({height, width});
auto res_buf = result.request();
auto *res_ptr = static_cast<uint8_t*>(res_buf.ptr);
// Алгоритм luminosity: 0.21 R + 0.72 G + 0.07 B
for (size_t y = 0; y < height; ++y) {
for (size_t x = 0; x < width; ++x) {
size_t idx = (y * width + x) * 3;
uint8_t r = ptr[idx];
uint8_t g = ptr[idx + 1];
uint8_t b = ptr[idx + 2];
res_ptr[y * width + x] = static_cast<uint8_t>(
0.21f * r + 0.72f * g + 0.07f * b
);
}
}
return result;
}
// Простой порогов Также в библиотеке
Работа с S3 и облачными хранилищами в C# (.NET): AWS SDK и MinIO
Конфигурация приложений Java: обзор библиотек и инструментов
Управление конфигурацией TypeScript-приложений: полное руководство