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.0
25.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]

Взаимодействие Python с C - comments

En
3 python c program (python)