Avatar ·

Вызов C/C++ из Python: как интегрировать нативный код в Python-проект?

Всем привет! Пишу на Python уже года два, но недавно столкнулся с задачей, которая меня поставила в тупик. Нужно ускорить один вычислительный модуль в моём проекте. На Python он работает слишком медленно (циклы на миллионах итераций), и я решил переписать критическую часть на C++. Написал простую функцию на C++ для вычисления чисел Фибоначчи: ```cpp // fibonacci.cpp #include extern "C" { int fibonacci(int n) { if (n <= 1) return n; int a = 0, b = 1, temp; for (int i = 2; i <= n; ++i) { temp = a + b; a = b; b = temp; } return b; } } ``` Скомпилировал в DLL через MinGW: `g++ -shared -o fibonacci.dll fibonacci.cpp` Теперь пытаюсь вызвать её из Python через ctypes: ```python import ctypes lib = ctypes.CDLL('./fibonacci.dll') result = lib.fibonacci(10) print(result) # Выводит 55 — работает! ``` Но это простой случай. А что если нужно передать массив данных? Или работать со строками? Или использовать классы C++ с методами? Я слышал про Cython, SWIG, pybind11, но не понимаю, какой подход выбрать для реального проекта. Можете подсказать, как правильно организовать вызов C/C++ из Python, чтобы это было надёжно, быстро и не слишком сложно? Желательно с примерами для разных сценариев: простые функции, массивы и структуры. <<>>

Сравнение подходов и практические примеры интеграции C/C++ с Python

Вы выбрали правильный путь — ctypes отлично подходит для простых функций, но для сложных сценариев (массивы, классы, строки) лучше использовать более мощные инструменты. Рассмотрим три основных подхода с примерами.

1. ctypes — для простых C-функций

Ваш пример с fibonacci работает, но важно указывать типы аргументов и возвращаемого значения:

import ctypes

lib = ctypes.CDLL('./fibonacci.dll')
lib.fibonacci.argtypes = [ctypes.c_int]
lib.fibonacci.restype = ctypes.c_int

print(lib.fibonacci(10))  # 55

Для массивов используйте указатели:

// C++ функция: void multiply_array(int* arr, int size, int factor)
extern "C" {
    void multiply_array(int* arr, int size, int factor) {
        for (int i = 0; i < size; ++i)
            arr[i] *= factor;
    }
}
# Python вызов
import ctypes

lib = ctypes.CDLL('./mylib.dll')
lib.multiply_array.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_int]
lib.multiply_array.restype = None

arr = (ctypes.c_int * 5)(1, 2, 3, 4, 5)
lib.multiply_array(arr, 5, 10)
print(list(arr))  # [10, 20, 30, 40, 50]

2. pybind11 — современный C++ интерфейс (рекомендуется)

Позволяет напрямую работать с классами, строками, STL-контейнерами. Установка: pip install pybind11.

// example.cpp
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <vector>
#include <string>

namespace py = pybind11;

class Calculator {
public:
    Calculator() {}
    
    int add(int a, int b) { return a + b; }
    
    std::vector<int> process_vector(const std::vector<int>& input) {
        std::vector<int> result;
        for (int v : input) result.push_back(v * 2);
        return result;
    }
    
    std::string greet(const std::string& name) {
        return "Hello, " + name + "!";
    }
};

PYBIND11_MODULE(mymodule, m) {
    py::class_<Calculator>(m, "Calculator")
        .def(py::init<>())
        .def("add", &Calculator::add)
        .def("process_vector", &Calculator::process_vector)
        .def("greet", &Calculator::greet);
}

Компиляция (Linux/Mac):

c++ -O3 -Wall -shared -std=c++11 -fPIC \
    $(python3 -m pybind11 --includes) \
    example.cpp -o mymodule$(python3-config --extension-suffix)

Использование в Python:

import mymodule

calc = mymodule.Calculator()
print(calc.add(5, 3))           # 8
print(calc.process_vector([1,2,3]))  # [2, 4, 6]
print(calc.greet("Python"))     # Hello, Python!

3. CFFI — для C-совместимых библиотек

Альтернатива ctypes с более чистым синтаксисом:

from cffi import FFI

ffi = FFI()
ffi.cdef("""
    int fibonacci(int n);
    void multiply_array(int* arr, int size, int factor);
""")

lib = ffi.dlopen('./mylib.dll')
print(lib.fibonacci(10))  # 55

arr = ffi.new("int[]", [1, 2, 3, 4, 5])
lib.multiply_array(arr, 5, 10)
print(list(arr))  # [10, 20, 30, 40, 50]

Рекомендации по выбору

  • ctypes — если нужно быстро вызвать пару C-функций без лишних зависимостей.
  • pybind11 — для серьёзных проектов с классами, STL, исключениями. Лучшая производительность и удобство.
  • CFFI — хороший компромисс между простотой и функциональностью.
  • Cython — если вы готовы писать на гибридном языке и нужна максимальная скорость.

Для вашей задачи с массивами и структурами я рекомендую pybind11 — он позволит передавать списки Python напрямую и работать с C++ классами как с обычными объектами Python.

<<>>

Альтернатива: Cython — пишем "почти Python", получаем C-скорость

Cython позволяет писать код, похожий на Python, который компилируется в C-расширение. Это отличный вариант, если вы не хотите писать обёртки отдельно, а готовы немного изменить стиль кода.

Пример для вашей задачи с массивами:

# fib_module.pyx
def fibonacci(int n):
    cdef int a = 0, b = 1, temp
    if n <= 1:
        return n
    for i in range(2, n+1):
        temp = a + b
        a = b
        b = temp
    return b

def multiply_array(arr, int factor):
    cdef int i
    cdef int size = len(arr)
    cdef int* c_arr = &arr[0]  # получаем указатель на данные
    for i in range(size):
        c_arr[i] *= factor

Компиляция через setup.py:

from setuptools import setup
from Cython.Build import cythonize

setup(ext_modules=cythonize("fib_module.pyx"))

Использование:

import fib_module
print(fib_module.fibonacci(10))  # 55

arr = [1, 2, 3, 4, 5]
fib_module.multiply_array(arr, 10)
print(arr)  # [10, 20, 30, 40, 50]

Плюсы: не нужно писать C++ код отдельно, всё в одном файле, типизация опциональна. Минусы: сложнее отладка и работа с внешними C++ библиотеками. Подходит, если ваш C++ код невелик и вы хотите сохранить Python-подобный синтаксис.