Как создать C-расширение для Python?python-21

C-расширения позволяют писать высокопроизводительные модули на C, которые можно импортировать прямо в Python. Рассмотрим процесс создания такого расширения.

Основные компоненты

  1. Python.h - главный заголовочный файл
  2. Модульный метод (Module Method) - функции, экспортируемые в Python
  3. Структура модуля (Module Structure) - описание расширения

Минимальный пример

#include <Python.h>

// Функция, которая будет доступна из Python
static PyObject* hello_world(PyObject* self, PyObject* args) {
    printf("Hello from C extension!\n");
    Py_RETURN_NONE;
}

// Описание методов модуля
static PyMethodDef module_methods[] = {
    {"hello_world", hello_world, METH_NOARGS, "Prints hello world"},
    {NULL, NULL, 0, NULL}  // Sentinel
};

// Описание модуля
static struct PyModuleDef mymodule = {
    PyModuleDef_HEAD_INIT,
    "mymodule",  // имя модуля
    NULL,        // документация
    -1,          // размер состояния модуля
    module_methods
};

// Инициализация модуля
PyMODINIT_FUNC PyInit_mymodule(void) {
    return PyModule_Create(&mymodule);
}

Сборка расширения

Для сборки нужно создать setup.py:

from setuptools import setup, Extension

module = Extension(
    'mymodule',
    sources=['mymodule.c'],
)

setup(
    name='MyModule',
    version='1.0',
    description='My C Extension',
    ext_modules=[module],
)

Собираем командой:

python setup.py build_ext --inplace

Работа с аргументами

static PyObject* add_numbers(PyObject* self, PyObject* args) {
    int a, b;

    // Парсим аргументы
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return NULL;  // Если аргументы неверные
    }

    return PyLong_FromLong(a + b);
}

Форматы аргументов:

  • "i" - integer
  • "f" - float
  • "s" - string
  • "O" - произвольный Python объект

Работа с исключениями

static PyObject* risky_operation(PyObject* self, PyObject* args) {
    if (error_condition) {
        PyErr_SetString(PyExc_RuntimeError, "Something went wrong");
        return NULL;
    }
    // Нормальное выполнение
    Py_RETURN_NONE;
}

Создание классов

typedef struct {
    PyObject_HEAD
    /* Тип-специфичные поля */
    int value;
} MyObject;

static PyTypeObject MyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "mymodule.MyType",
    .tp_basicsize = sizeof(MyObject),
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_new = PyType_GenericNew,
};

// В PyInit_mymodule:
PyType_Ready(&MyType);
PyModule_AddObject(module, "MyType", (PyObject*)&MyType);

Оптимизация производительности

  1. Используйте PyPy для JIT-компиляции
  2. Минимизируйте переходы между Python и C
  3. Для числовых операций рассмотрите Cython или Numba

Альтернативные подходы

  1. Cython - надстройка над C-расширениями
  2. cffi - более простой интерфейс для вызова C
  3. PyBind11 - современный C++ интерфейс
from cffi import FFI
ffi = FFI()
ffi.cdef("int add(int a, int b);")
lib = ffi.dlopen("./mylib.so")
print(lib.add(2, 3))

Резюмируем

создание C-расширений требует хорошего знания Python C API, но позволяет достичь максимальной производительности. Для большинства задач лучше начинать с более простых решений вроде Cython или cffi, а к "чистым" C-расширениям переходить только при необходимости тонкой оптимизации.