Создание модулей расширения CPython на языке C

Раздел: Расширения -> Расширения на C

Основные подходы для создания расширений на C

Как разработать Python-расширение на C с максимальной производительностью?

Python C API предоставляет прямой доступ к внутренностям интерпретатора. Это наиболее эффективный способ создания расширений, но требует глубокого понимания управления памятью и типами CPython.

Цель: Создать модуль mymath, содержащий функцию add(x,y), возвращающую сумму.

Шаг 1. Создайте файл mymath.c:

#include <Python.h>

static PyObject* mymath_add(PyObject* self, PyObject* args) {
    double a, b;
    if (!PyArg_ParseTuple(args, "dd", &a, &b))
        return NULL;
    double result = a + b;
    return PyFloat_FromDouble(result);
}

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

static struct PyModuleDef mymathmodule = {
    PyModuleDef_HEAD_INIT,
    "mymath",
    NULL,
    -1,
    MyMathMethods
};

PyMODINIT_FUNC PyInit_mymath(void) {
    return PyModule_Create(&mymathmodule);
}

модули расширения python (модули расширения python (c extensions))

Шаг 2. Создайте setup.py для сборки:

from setuptools import setup, Extension

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

setup(name='mymath',
      version='1.0',
      description='Example extension',
      ext_modules=[module])

Шаг 3. Соберите и установите: python setup.py build_ext --inplace

Шаг 4. Проверьте в Python:

import mymath
print(mymath.add(2.5, 3.7))
6.2

Типичные проблемы:

  • Ошибка линковки из-за отсутствия Python.h. Установите python-dev или python3-dev.
  • Несоответствие типов в PyArg_ParseTuple. Используйте правильные коды формата (d – double, i – int, s – string).
  • Утечки памяти: при создании новых объектов Python вызывайте Py_DECREF при необходимости.
  • Краш при импорте на Windows из-за неправильного экспорта символов. Добавьте __declspec(dllexport) в PyInit_ функцию.

Как упростить создание расширения с помощью Cython?

Cython позволяет писать код на Python с аннотациями типов и транслировать его в C. Это значительно проще, чем ручное написание C API, и даёт почти нативную производительность.

Пример: тот же модуль mymath.

# mymath.pyx
def add(double a, double b):
    return a + b
# setup.py
from setuptools import setup
from Cython.Build import cythonize

setup(ext_modules=cythonize("mymath.pyx"))

Сборка: python setup.py build_ext --inplace

Использование идентично.

Проблемы: Cython генерирует большой .c файл; отладка требует сравнения сгенерированного кода; статические типы могут ограничивать динамические возможности Python.

Как вызывать C-функции без написания полноценного расширения?

ctypes – стандартная библиотека Python для вызова разделяемых библиотек. Не требует компиляции расширения, но менее производительная из-за накладных расходов на преобразование типов.

Пример: предположим, у нас есть библиотека libmymath.so с функцией double add(double, double).

from ctypes import CDLL

lib = CDLL("./libmymath.so")
lib.add.argtypes = [ctypes.c_double, ctypes.c_double]
lib.add.restype = ctypes.c_double
print(lib.add(2.5, 3.7))
6.2

Проблемы: Необходимость вручную определять argtypes и restype; ошибки сегментации при несовпадении типов; сложно работать со сложными структурами и массивами.

Как интегрировать C-библиотеку с помощью CFFI?

CFFI (C Foreign Function Interface) – более мощная альтернатива ctypes, поддерживает встраивание C-кода прямо в Python и автоматическую генерацию обёрток.

from cffi import FFI

ffi = FFI()
# Описываем сигнатуру
ffi.cdef("double add(double, double);")
# Загружаем библиотеку
lib = ffi.dlopen("./libmymath.so")
result = lib.add(2.5, 3.7)
print(result)
6.2

Проблемы: Требуется установка пакета cffi; для сложных ABI может потребоваться написание .h файлов.

Расширенные примеры и нестандартные случаи

Пример 1: Модуль с обработкой списков (Python C API)

Создадим функцию sum_list, принимающую список чисел и возвращающую сумму всех элементов.

Пример
#include <Python.h>

static PyObject* sum_list(PyObject* self, PyObject* args) {
    PyObject* list;
    if (!PyArg_ParseTuple(args, "O!", &PyList_Type, &list))
        return NULL;
    Py_ssize_t len = PyList_Size(list);
    double total = 0.0;
    for (Py_ssize_t i = 0; i < len; i++) {
        PyObject* item = PyList_GetItem(list, i);
        if (PyFloat_Check(item))
            total += PyFloat_AsDouble(item);
        else if (PyLong_Check(item))
            total += (double)PyLong_AsLong(item);
        else {
            PyErr_SetString(PyExc_TypeError, "list must contain numbers");
            return NULL;
        }
    }
    return PyFloat_FromDouble(total);
}

// ... (таблица методов и инициализация аналогично)

Сборка и тест:

Пример
import mymath
print(mymath.sum_list([1.5, 2, 3.2, 4]))
10.7

Пример 2: Cython с typed memoryview для быстрой обработки массивов

Пример
# sum_array.pyx
def sum_array(double[:] arr):
    cdef:
        int i
        double total = 0
    for i in range(arr.shape[0]):
        total += arr[i]
    return total

Использование:

Пример
import numpy as np
import sum_array
arr = np.array([1.0, 2.0, 3.0], dtype=np.float64)
print(sum_array.sum_array(arr))
6.0
При использовании memoryview нужно обязательно передавать массив с правильным типом и порядком байтов.

Пример 3: ctypes – работа со структурами и указателями

Пример
from ctypes import Structure, c_double, POINTER

class Point(Structure):
    _fields_ = [("x", c_double), ("y", c_double)]

lib = CDLL("./libgeom.so")
# Функция double distance(Point* p1, Point* p2)
lib.distance.argtypes = [POINTER(Point), POINTER(Point)]
lib.distance.restype = c_double

p1 = Point(0.0, 0.0)
p2 = Point(3.0, 4.0)
print(lib.distance(p1, p2))
5.0

Пример 4: CFFI с обратными вызовами (callback)

Пример
from cffi import FFI

ffi = FFI()
ffi.cdef("""
    typedef double (*callback_t)(double);
    double apply_callback(callback_t cb, double x);
""")
lib = ffi.dlopen("./libcallback.so")

# Создаём callback на Python
@ffi.callback("double(double)")
def square(x):
    return x * x

result = lib.apply_callback(square, 5.0)
print(result)
25.0

Модули расширения Python (C extensions) - comments

En
модули расширения python (python)