Объединение Python с C в окружении Linux

Раздел: Операционная система и окружение -> Linux

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

Как вызвать функцию на C из Python с минимальными усилиями?

Основной подход: ctypes

ctypes позволяет загружать динамические библиотеки и вызывать функции. Цель: быстрое прототипирование и работа с существующими библиотеками. Случаи использования: вызов системных библиотек, работа с аппаратурой.

// example.c
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

Python c linux (python, c и linux)

gcc -shared -o libexample.so -fPIC example.c
# python_ctypes.py
import ctypes

lib = ctypes.CDLL('./libexample.so')
lib.add.argtypes = (ctypes.c_int, ctypes.c_int)
lib.add.restype = ctypes.c_int
result = lib.add(3, 5)
print(f"Result: {result}")
Result: 8
Типичная ошибка: OSError: ... cannot open shared object file. Решение: указать полный путь или установить LD_LIBRARY_PATH. Также возможна ошибка из-за 32/64-битной несовместимости.

Как получить более высокую производительность и типовую безопасность?

Вариант: CFFI

CFFI предоставляет чистый интерфейс к C, поддерживает обратные вызовы и работает быстрее ctypes в некоторых сценариях. Цель: проекты, требующие надежности и скорости. Случаи использования: научные вычисления, встраиваемые системы.

# example_build.py
from cffi import FFI
ffi = FFI()
ffi.cdef("int add(int, int);")
lib = ffi.dlopen('./libexample.so')
result = lib.add(2, 3)
print(result)
5
CFFI может потребовать установки python3-dev и libffi-dev. Ошибка компиляции при использовании API без cdef.

Как интегрировать C код напрямую в Python, избегая отдельной компиляции?

Вариант: Cython

Cython позволяет писать код на Python с аннотациями типов, который транслируется в C. Цель: оптимизация критических участков. Случаи использования: численные алгоритмы, работа с большими массивами.

# example.pyx
def add(int a, int b):
    return a + b
# setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize("example.pyx"))
$ python setup.py build_ext --inplace
# python_call.py
import example
print(example.add(10, 20))
30
Требуется установка Cython. Ошибки при указании типов: несоответствие типов C и Python. Необходимо следить за версиями Python и Cython.

Как создать расширение на C с полным доступом к Python API?

Вариант: Python C API

Позволяет писать расширения, которые являются полноценными модулями Python. Цель: максимальная производительность и управление памятью. Случаи использования: библиотеки для работы с изображениями, криптография.

// module.c
#include <Python.h>

static PyObject* add(PyObject* self, PyObject* args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) return NULL;
    return PyLong_FromLong(a + b);
}

static PyMethodDef methods[] = {
    {"add", add, METH_VARARGS, "Add two numbers"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT, "mymodule", NULL, -1, methods
};

PyMODINIT_FUNC PyInit_mymodule(void) {
    return PyModule_Create(&module);
}
# setup.py
from setuptools import setup, Extension
module = Extension('mymodule', sources=['module.c'])
setup(name='mymodule', ext_modules=[module])
$ python setup.py build_ext --inplace
# python_use.py
import mymodule
print(mymodule.add(7, 8))
15
Частые ошибки: утечка памяти при некорректном управлении ссылками; ошибки линковки из-за несовпадения ABI; сложность отладки сегфолтов.

Как выполнить скомпилированную C программу как внешний процесс?

Вариант: subprocess

Вызов готовой C-программы через оболочку. Цель: простота, изоляция. Случаи использования: запуск утилит командной строки, обработка данных через конвейеры.

// example_add.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
    if (argc != 3) { fprintf(stderr, "Usage: %s a b\n", argv[0]); return 1; }
    int a = atoi(argv[1]); int b = atoi(argv[2]);
    printf("%d\n", a + b);
    return 0;
}
# call_subprocess.py
import subprocess
result = subprocess.run(['./add', '10', '20'], capture_output=True, text=True)
print(result.stdout.strip())
30
Проблемы: накладные расходы на создание процесса; сложность передачи сложных структур данных; коды возврата и обработка ошибок.

Расширенные примеры интеграции Python и C

Пример с ctypes и структурами

Пример
// point.c
typedef struct { int x; int y; } Point;
Point create_point(int x, int y) { Point p = {x,y}; return p; }
int distance(Point p) { return p.x*p.x + p.y*p.y; }
Пример
# python_ctypes_struct.py
import ctypes

class Point(ctypes.Structure):
    _fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)]

lib = ctypes.CDLL('./libpoint.so')
lib.create_point.restype = Point
lib.create_point.argtypes = [ctypes.c_int, ctypes.c_int]
p = lib.create_point(3, 4)
print(p.x, p.y)
lib.distance.argtypes = [Point]
lib.distance.restype = ctypes.c_int
print(lib.distance(p))
3 4
25

Пример с CFFI и обратными вызовами

Пример
// callback.c
#include <stdio.h>
void apply(int (*f)(int), int arr[], int n) {
    for (int i=0; i<n; i++) arr[i] = f(arr[i]);
}
Пример
# python_cffi_callback.py
from cffi import FFI
ffi = FFI()
ffi.cdef("void apply(int (*)(int), int[], int);")
lib = ffi.dlopen('./libcallback.so')
arr = ffi.new("int[]", [1,2,3,4])
@ffi.callback("int(int)")
def square(x):
    return x*x
lib.apply(square, arr, 4)
print(list(arr))
[1, 4, 9, 16]

Расширение с помощью Cython и использование numpy

Пример
# cython_numpy.pyx
import numpy as np
cimport numpy as np

def sum_ndarray(np.ndarray[np.int32_t, ndim=1] arr):
    cdef int i, total=0
    for i in range(arr.shape[0]):
        total += arr[i]
    return total
Пример
# python_usage.py
import pyximport; pyximport.install()
import cython_numpy
import numpy as np
a = np.array([1,2,3,4,5], dtype=np.int32)
print(cython_numpy.sum_ndarray(a))
15

Python C API: работа с исключениями и объектами

Пример
// exception.c
#include <Python.h>
static PyObject* divide(PyObject* self, PyObject* args) {
    double a, b;
    if (!PyArg_ParseTuple(args, "dd", &a, &b)) return NULL;
    if (b == 0.0) {
        PyErr_SetString(PyExc_ZeroDivisionError, "division by zero");
        return NULL;
    }
    return PyFloat_FromDouble(a / b);
}

static PyMethodDef methods[] = {
    {"divide", divide, METH_VARARGS, "Divide two numbers"},
    {NULL, NULL, 0, NULL}
};

static struct PyModuleDef module = {
    PyModuleDef_HEAD_INIT, "myexcept", NULL, -1, methods
};

PyMODINIT_FUNC PyInit_myexcept(void) {
    return PyModule_Create(&module);
}
Пример
# setup.py
from setuptools import setup, Extension
module = Extension('myexcept', sources=['exception.c'])
setup(name='myexcept', ext_modules=[module])
Пример
# python_use_except.py
import myexcept
print(myexcept.divide(10, 2))
try:
    myexcept.divide(1,0)
except ZeroDivisionError as e:
    print(e)
5.0
division by zero

Подход с использованием subprocess и передача больших данных через stdin/stdout

Пример
// filter.c
#include <stdio.h>
#define BUFSIZE 1024
int main() {
    char buf[BUFSIZE];
    while (fgets(buf, BUFSIZE, stdin)) {
        int x = atoi(buf);
        printf("%d\n", x*2);
        fflush(stdout);
    }
    return 0;
}
Пример
# pipe_data.py
import subprocess
import numpy as np
data = [1,2,3,4,5]
proc = subprocess.Popen(['./filter'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
out, _ = proc.communicate('\n'.join(map(str,data)) + '\n')
result = [int(line) for line in out.strip().split('\n')]
print(result)
[2, 4, 6, 8, 10]

Python, C и Linux - comments

En
Python c linux (python)