Python и C: эффективное взаимодействие через библиотеки
Взаимодействие Python с языком C открывает доступ к высокой производительности и существующим библиотекам. На практике применяются три основных подхода: ctypes, Cython и cffi. Каждый из них имеет свои сильные стороны и сферу применения. Ниже рассмотрен наиболее производительный метод - Cython, а затем альтернативные варианты.
Методы взаимодействия Python с C
Как добиться максимальной производительности при вызове C функций из Python?
Cython - компилятор, который транслирует Python-подобный код в C расширения. Он позволяет писать смешанный код, где узкие места реализуются на C, а обвязка остаётся на Python. Для примера возьмём функцию сложения двух чисел на C:
// mathlib.c
#include <stdio.h>
double add(double a, double b) {
return a + b;
}3 python c program (взаимодействие python с c)
# mathlib.h (опционально)
double add(double a, double b);Создадим файл add_cy.pyx:
cdef extern from "mathlib.h":
double add(double a, double b)
def py_add(double a, double b):
return add(a, b)Далее требуется файл setup.py:
from setuptools import setup, Extension
from Cython.Build import cythonize
setup(
ext_modules = cythonize([
Extension("add_cy", sources=["add_cy.pyx", "mathlib.c"])
])
)Компиляция выполняется командой:
python setup.py build_ext --inplaceПосле этого модуль можно импортировать в Python:
import add_cy
print(add_cy.py_add(3.0, 4.0)) # 7.0Типичная ошибка - отсутствие установленного Cython. Решение: pip install cython. Также могут возникать ошибки линковки, если не указать путь к библиотеке. В таком случае добавляют параметры library_dirs и libraries в Extension.
Как быстро интегрировать любую C библиотеку без написания обёрток на C?
ctypes - встроенный модуль, позволяющий загружать динамические библиотеки (.so, .dll) и вызывать функции напрямую. Для функции add из предыдущего примера:
from ctypes import CDLL
lib = CDLL("./libmathlib.so") # или .dll для Windows
lib.add.argtypes = [ctypes.c_double, ctypes.c_double]
lib.add.restype = ctypes.c_double
result = lib.add(3.0, 4.0)
print(result) # 7.0Ошибки: неправильно указаны типы аргументов или возвращаемого значения - функция может вернуть мусор. Решение: точно задать argtypes и restype. Также нужно убедиться, что библиотека собрана с корректным вызовом (cdecl или stdcall).
Какая альтернатива ctypes даёт близкую к Cython производительность без изменения синтаксиса?
cffi - библиотека для вызова C функций из Python. Она быстрее ctypes за счёт генерации C кода на лету. Использование для той же функции:
from cffi import FFI
ffi = FFI()
ffi.cdef("double add(double, double);")
lib = ffi.dlopen("./libmathlib.so")
result = lib.add(3.0, 4.0)
print(result) # 7.0Проблемы: требуется установка cffi (pip install cffi). Если библиотека использует сложные структуры или обратные вызовы, потребуется дополнительная настройка. Для Windows может понадобиться указать полный путь к DLL.
Цели и случаи использования: ctypes подходит для быстрого прототипирования и работы с простыми функциями; Cython - для узких мест, требующих высокой производительности; cffi - как компромисс между производительностью и простотой написания.
Расширенные примеры с результатами
1. Cython: передача массивов и изменение данных на месте
Пример умножения элементов массива на константу в C и вызов из Python.
// array_ops.c
void scale_array(double *arr, int n, double factor) {
for (int i = 0; i < n; i++)
arr[i] *= factor;
}# scale.pyx
cdef extern from "array_ops.h":
void scale_array(double *arr, int n, double factor)
def scale(double[:] arr, double factor):
scale_array(&arr[0], arr.shape[0], factor)import numpy as np
import scale
a = np.array([1.0, 2.0, 3.0], dtype=np.float64)
scale.scale(a, 10.0)
print(a) # [10. 20. 30.][10. 20. 30.]
2. ctypes: работа со структурами
Определение C структуры и её создание в Python.
// point.h
typedef struct {
double x;
double y;
} Point;
Point* create_point(double x, double y);
void print_point(Point* p);from ctypes import *
class Point(Structure):
_fields_ = [("x", c_double), ("y", c_double)]
lib = CDLL("./libpoint.so")
lib.create_point.restype = POINTER(Point)
lib.print_point.argtypes = [POINTER(Point)]
p = lib.create_point(1.5, 2.5)
print(p.contents.x, p.contents.y) # 1.5 2.5
lib.print_point(p)1.5 2.5
3. cffi: обратные вызовы (callback)
Передача функции Python в C функцию ожидающую указатель.
// callback.c
typedef double (*callback_t)(double);
double apply(double x, callback_t cb) {
return cb(x);
}from cffi import FFI
ffi = FFI()
ffi.cdef("""
typedef double (*callback_t)(double);
double apply(double x, callback_t cb);
""")
lib = ffi.dlopen("./libcallback.so")
@ffi.callback("double(double)")
def square(x):
return x * x
result = lib.apply(5.0, square)
print(result) # 25.025.0
4. Cython: многомодульная сборка с использованием Numpy
Объединение нескольких .pyx файлов и использование Numpy типов.
# convolve.pyx
import numpy as np
cimport numpy as np
cdef extern from "convolve.h":
void convolve_c(double* a, double* b, double* out, int n)
def convolve(np.ndarray[np.float64_t, ndim=1] a, np.ndarray[np.float64_t, ndim=1] b):
out = np.zeros(a.shape[0] + b.shape[0] - 1, dtype=np.float64)
convolve_c(&a[0], &b[0], &out[0], a.shape[0])
return out# setup.py
from setuptools import setup, Extension
from Cython.Build import cythonize
import numpy
setup(
ext_modules = cythonize([
Extension("convolve", sources=["convolve.pyx", "convolve.c"])
]),
include_dirs=[numpy.get_include()]
)import numpy as np
import convolve
a = np.array([1,2,3], dtype=np.float64)
b = np.array([0.5, 0.5], dtype=np.float64)
print(convolve.convolve(a, b))[0.5 1.5 2.5 1.5]