Интеграция C и Python: вызов нативных библиотек
Загрузка C библиотеки в Python: способы и примеры
При разработке на Python часто возникает необходимость использовать существующий код на C или C++. Это может быть связано с требованиями производительности, доступом к низкоуровневым системным вызовам или интеграцией с уже написанными библиотеками. Python предлагает несколько механизмов для загрузки и взаимодействия с разделяемыми библиотеками (DLL, SO, Dylib). В этой статье рассматриваются основные подходы, их преимущества и подводные камни.
Как загрузить разделяемую библиотеку на C и вызвать функцию в Python?
Наиболее распространённым и универсальным решением является модуль ctypes, входящий в стандартную библиотеку Python. Он позволяет динамически загружать библиотеки, определять типы аргументов и возвращаемых значений, а также вызывать функции без необходимости компиляции дополнительного кода.
import ctypes
# Загрузка библиотеки (путь может быть абсолютным или относительным)
lib = ctypes.CDLL("./libsample.so") # Linux/Mac
# или lib = ctypes.CDLL("sample.dll") # Windows
# Определение прототипа функции (тип возвращаемого значения и аргументов)
lib.my_function.restype = ctypes.c_int
lib.my_function.argtypes = [ctypes.c_int, ctypes.c_double]
# Вызов функции
result = lib.my_function(42, 3.14)
print(result)
Python list in c (использование списков python в c)
(пример вывода, зависящий от реализации функции)
Use python in c (использование python в коде c (встраивание))
Пояснение шагов:
- Импорт модуля ctypes.
- Создание объекта библиотеки с помощью CDLL (для Windows – windll или oledll). Путь может быть относительным или полным; можно использовать ctypes.util.find_library для поиска в системе.
- Указание типа возвращаемого значения через restype. Если функция возвращает void, используется None.
- Определение типов аргументов через argtypes – список типов из ctypes (c_int, c_double, c_char_p и т.д.). Это необязательно, но улучшает контроль и предотвращает ошибки.
- Вызов функции как обычного метода объекта библиотеки.
Типичные ошибки:
- OSError: cannot open shared object file – библиотека не найдена. Проверить путь, переменную среды LD_LIBRARY_PATH (Linux) или PATH (Windows).
- AttributeError: function not found – имя функции не совпадает с экспортированным (например, из-за name mangling в C++). Для C функций нужно использовать extern "C" или точное имя.
- Ошибки при несовпадении типов: segfault или неверные результаты. Рекомендуется всегда задавать argtypes и restype.
- Проблемы с кодировкой строк: ctypes по умолчанию передаёт строки как bytes. Если функция ожидает const char* в UTF-8, используйте c_char_p.
Этот метод подходит для большинства случаев, когда библиотека уже скомпилирована и не требует изменения кода на C.
Как использовать CFFI для более чистого интерфейса?
Модуль cffi (C Foreign Function Interface) предлагает альтернативный способ, при котором описание интерфейса задаётся в виде строки с декларациями на C. Это обеспечивает лучшую читаемость и более прямой контроль над типами.
from cffi import FFI
ffi = FFI()
# Описание функций и типов на C
ffi.cdef("""
int my_function(int a, double b);
""")
# Загрузка библиотеки
lib = ffi.dlopen("./libsample.so")
# Вызов
result = lib.my_function(42, 3.14)
print(result)
Python c types (библиотека ctypes в python)
Проблемы: CFFI не входит в стандартную библиотеку, требуется установка (pip install cffi). При компиляции C кода (API mode) необходимо иметь компилятор. Однако для скомпилированных библиотек это не требуется.
Как скомпилировать C код в расширение Python с помощью Cython?
Cython позволяет писать код, похожий на Python, но компилируемый в C-расширение. Таким образом можно не только вызывать C-функции, но и создавать эффективные обёртки.
# example.pyx
cdef extern from "sample.h":
int my_function(int a, double b)
def call_my_function(a, b):
return my_function(a, b)
Python load c lib (загрузка c библиотеки в python)
Для сборки используется setup.py или pyximport. После компиляции получается обычный модуль Python.
Ошибки: требуется компилятор C и установленный Cython. Ошибки часто связаны с неверными путями к заголовочным файлам или несовместимостью типов.
Как создать нативные Python модули из C++ с pybind11?
Pybind11 – современная библиотека для создания биндингов C++ в Python. Она минимизирует шаблонный код и поддерживает множество возможностей C++.
#include <pybind11/pybind11.h>
int my_function(int a, double b) {
return static_cast<int>(a + b);
}
PYBIND11_MODULE(example, m) {
m.doc() = "example module";
m.def("my_function", &my_function, "A function that adds");
}
Компиляция через CMake или setup.py с pybind11. На выходе – модуль .so/.pyd.
Проблемы: сложность сборки, зависимость от pybind11 и компилятора C++11/14. Ошибки линковки часто связаны с неверными флагами или версиями Python.
Как автоматически сгенерировать обертку ctypes из заголовочного файла?
Инструмент ctypesgen читает C-заголовок и генерирует Python модуль с использованием ctypes. Это удобно для больших библиотек.
ctypesgen -l sample sample.h -o sample_wrapper.py
После генерации можно импортировать готовый модуль.
Ошибки: некорректные объявления в заголовке (особенно макросы, сложные typedef). Иногда требуется ручная корректировка сгенерированного кода.
Расширенные примеры загрузки C библиотек
Передача структур и указателей через ctypes
Определим структуру на C и функцию, которая её заполняет.
// sample.h
typedef struct {
int x;
double y;
} Point;
void fill_point(Point* p, int x, double y);
import ctypes
class Point(ctypes.Structure):
_fields_ = [("x", ctypes.c_int), ("y", ctypes.c_double)]
lib = ctypes.CDLL("./libsample.so")
lib.fill_point.argtypes = [ctypes.POINTER(Point), ctypes.c_int, ctypes.c_double]
lib.fill_point.restype = None
p = Point()
lib.fill_point(ctypes.byref(p), 10, 20.5)
print(p.x, p.y) # 10 20.5
10 20.5
Коллбэки (callback) из Python в C
В C определена функция, принимающая указатель на функцию.
// sample.h
typedef int (*callback_t)(int);
void set_callback(callback_t cb);
CALLBACK = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int)
def my_callback(x):
return x * 2
lib = ctypes.CDLL("./libsample.so")
lib.set_callback.argtypes = [CALLBACK]
lib.set_callback.restype = None
# Преобразуем Python функцию в C-совместимый указатель
cb = CALLBACK(my_callback)
lib.set_callback(cb)
Работа с динамическими строками (возврат malloc'ed string)
// Возвращает строку, которую нужно освободить free()
char* get_message();
void free_message(char* msg);
lib = ctypes.CDLL("./libsample.so")
lib.get_message.restype = ctypes.c_char_p
lib.free_message.argtypes = [ctypes.c_char_p]
lib.free_message.restype = None
msg_ptr = lib.get_message() # возвращает указатель на C-строку
msg = ctypes.string_at(msg_ptr) # копируем в Python bytes
print(msg)
lib.free_message(msg_ptr) # освобождаем память
b'Hello from C!'
Использование ctypes.util.find_library
import ctypes.util
lib_path = ctypes.util.find_library("c") # найдёт libc.so или msvcrt.dll
if lib_path:
libc = ctypes.CDLL(lib_path)
print(libc.rand()) # пример вызова случайного числа
1804289383 (зависит от RNG)
Загрузка библиотеки с зависимостями (RPATH, LD_LIBRARY_PATH)
import os
os.environ["LD_LIBRARY_PATH"] = "/opt/mylib/lib:" + os.environ.get("LD_LIBRARY_PATH", "")
# После этого загружать библиотеку стандартным способом
Пример CFFI с встроенным C-кодом
from cffi import FFI
ffi = FFI()
# Можно скомпилировать inline C код (требуется компилятор C)
ffi.set_source("_example",
r"""
int my_function(int a, double b) {
return (int)(a + b);
}
""")
ffi.compile()
# После компиляции импортируем
from _example import lib
print(lib.my_function(1, 2.5))
3
Пример Cython с прямым вызовом
# example.pyx
cdef extern from "stdlib.h":
int rand()
def get_random():
return rand()
Сборка: cythonize -i example.pyx (требует Cython и компилятор).