Примеры взаимодействия Python и C
Примеры вызова C из Python
Какие инструменты позволяют вызывать функции, написанные на C, непосредственно из кода Python?
Наиболее распространённое и эффективное решение для вызова C кода из Python - использование модуля ctypes. Этот модуль входит в стандартную библиотеку Python, не требует дополнительной установки и поддерживает вызов функций из динамических библиотек (DLL, .so, .dylib). Ctypes автоматически преобразует типы данных между Python и C, но требует явного указания сигнатур функций.
Пример: пусть имеется файл math_ops.c с простой функцией сложения:
// math_ops.c
#include <stdint.h>
int32_t add(int32_t a, int32_t b) {
return a + b;
}Python c code (примеры вызова c из python)
Компиляция в разделяемую библиотеку (Linux):
gcc -shared -o libmath_ops.so -fPIC math_ops.cC вызов python (вызов python из c)
Вызов из Python с помощью ctypes:
import ctypes
lib = ctypes.CDLL('./libmath_ops.so')
# Указываем типы аргументов и возвращаемого значения
lib.add.argtypes = [ctypes.c_int32, ctypes.c_int32]
lib.add.restype = ctypes.c_int32
result = lib.add(10, 15)
print(result) # 25
библиотека dll python (библиотека для работы с dll в python)
При работе с ctypes важно правильно задавать типы: несоответствие может привести к неверным результатам или ошибкам сегментации.
Как упростить интеграцию C кода с помощью CFFI?
Библиотека CFFI (C Foreign Function Interface) предлагает более гибкий и читаемый способ описания интерфейса. Она поддерживает как вызов готовых библиотек, так и встраивание C кода прямо в Python. CFFI бывает двух режимов: API (для упаковки в pip-пакет) и ABL (для одноразовых вызовов).
Пример с тем же libmath_ops.so:
from cffi import FFI
ffi = FFI()
# Описываем прототип функции
ffi.cdef("int32_t add(int32_t a, int32_t b);")
lib = ffi.dlopen('./libmath_ops.so')
result = lib.add(10, 15)
print(result) # 25CFFI автоматически подбирает типы, если они соответствуют стандартным. Однако для сложных структур требуется больше описаний.
Как получить максимальную производительность при вызове C из Python?
Для задач, где критична скорость, эффективно использовать Cython. Cython позволяет писать код на Python-подобном языке, который транслируется в C расширение. Это даёт возможность напрямую вызывать C функции без накладных расходов на преобразование типов.
Создадим файл cy_add.pyx:
cdef extern from "math_ops.c":
int32_t add(int32_t a, int32_t b)
def py_add(int a, int b):
return add(a, b)Файл setup.py для сборки:
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("cy_add.pyx")
)Сборка: python setup.py build_ext --inplace. После этого можно импортировать:
from cy_add import py_add
print(py_add(10, 15)) # 25Типичные проблемы и их решения
- Ошибка сегментации при передаче указателей - возникает при неверном определении аргументов. Решение: всегда проверять соответствие типов и убедиться, что память выделена корректно (например, через ctypes.create_string_buffer).
- Несовместимость соглашений о вызовах (cdecl vs stdcall) - на Windows ctypes по умолчанию использует CDLL (cdecl) для библиотек, собранных GCC, и WinDLL (stdcall) для системных DLL. Неверный выбор приводит к краху.
- Утечка памяти при работе с C-функциями, выделяющими память (malloc). Решение: обязательно освобождать выделенную память через соответствующую функцию C (free) после использования.
Расширенные примеры вызова C из Python
Работа с массивами и указателями через ctypes
Допустим, функция на C принимает массив чисел и его длину:
// array_sum.c
#include <stdint.h>
int64_t sum_array(const int32_t *arr, size_t len) {
int64_t total = 0;
for (size_t i = 0; i < len; ++i)
total += arr[i];
return total;
}Компиляция: gcc -shared -o libarray.so -fPIC array_sum.c
Вызов из Python с передачей массива:
import ctypes
lib = ctypes.CDLL('./libarray.so')
lib.sum_array.argtypes = [ctypes.POINTER(ctypes.c_int32), ctypes.c_size_t]
lib.sum_array.restype = ctypes.c_int64
# Создаём массив Python и конвертируем в C-массив
py_arr = [1, 2, 3, 4, 5]
c_arr = (ctypes.c_int32 * len(py_arr))(*py_arr)
result = lib.sum_array(c_arr, len(py_arr))
print(result) # 15Результат выполнения:
15
Передача строк и работа с памятью в CFFI
Функция, возвращающая строку из C (с выделением памяти):
// hello.c
#include <stdlib.h>
#include <string.h>
char* get_greeting(const char *name) {
char *msg = malloc(100);
snprintf(msg, 100, "Hello, %s!", name);
return msg;
}
void free_msg(char *msg) {
free(msg);
}Компиляция: gcc -shared -o libhello.so -fPIC hello.c
Использование CFFI (ABI режим):
from cffi import FFI
ffi = FFI()
ffi.cdef("""
char* get_greeting(const char *name);
void free_msg(char *msg);
""")
C = ffi.dlopen('./libhello.so')
c_name = ffi.new("char[]", b"World")
c_greet = C.get_greeting(c_name)
# Преобразуем C-строку в bytes Python
greet = ffi.string(c_greet)
print(greet.decode()) # Hello, World!
# Освобождаем память
C.free_msg(c_greet)Результат:
Hello, World!
Важно: всегда освобождать память, выделенную в C, иначе утечка.
Обратные вызовы (callbacks) из C в Python
Функция C принимает указатель на функцию обратного вызова.
// callback.c
#include <stdint.h>
typedef void (*callback_t)(int32_t);
void repeat(int32_t n, callback_t cb) {
for (int32_t i = 0; i < n; ++i)
cb(i);
}Компиляция: gcc -shared -o libcallback.so -fPIC callback.c
Вызов из Python с ctypes:
import ctypes
lib = ctypes.CDLL('./libcallback.so')
# Определяем тип callback
CALLBACK = ctypes.CFUNCTYPE(None, ctypes.c_int32)
def my_callback(value):
print(f"Callback called with {value}")
lib.repeat.argtypes = [ctypes.c_int32, CALLBACK]
lib.repeat.restype = None
lib.repeat(3, my_callback)Результат:
Callback called with 0 Callback called with 1 Callback called with 2
Проблема: если callback вызывается из другого потока C, может потребоваться установка флага ctypes.pythonapi.PyGILState_Ensure для безопасности.