Объединение Python с C в окружении 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
Как получить более высокую производительность и типовую безопасность?
Вариант: 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
Как интегрировать 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
Как создать расширение на 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
Как выполнить скомпилированную 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]