Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Industrial programming >> Python

Расширение Python на C: создание быстрых нативных модулей

Предварительные требования для написания расширений

Любой код, написанный на компилируемых языках (C, C++, Java), может быть интегрирован или импортирован в другой Python-скрипт. Такой код считается «расширением».

Модуль расширения Python — это обычная C-библиотека. На Unix-машинах такие библиотеки обычно заканчиваются на .so (shared object). На Windows — .dll (dynamically linked library).

Для начала написания расширения нужны заголовочные файлы Python.

Кроме того, предполагается, что у вас есть хорошие знания C или C++ для написания расширений Python на C.

Первый взгляд на расширение Python

Для первого взгляда на модуль расширения Python нужно сгруппировать код в четыре части:

Заголовочный файл Python.h

Нужно включить Python.h в ваш C-файл, что дает доступ к внутреннему Python API для подключения модуля к интерпретатору.

Убедитесь, что Python.h включен перед любыми другими заголовками. После включений следуют функции, которые вы хотите вызывать из Python.

C-функции

Подписи C-реализации ваших функций всегда принимают одну из трех форм:

static PyObject *MyFunction(PyObject *self, PyObject *args);
static PyObject *MyFunctionWithKeywords(PyObject *self, PyObject *args, PyObject *kw);
static PyObject *MyFunctionWithNoArgs(PyObject *self);

Каждое из объявлений возвращает объект Python. В Python нет функций void, как в C. Если функция не должна возвращать значение, верните C-эквивалент Python'овского None. Заголовочные файлы Python определяют макрос Py_RETURN_NONE для этого.

Имена ваших C-функций могут быть любыми, так как они никогда не видны вне модуля расширения. Они определяются как static функции.

Обычно C-функции называют, комбинируя имя Python-модуля и имя функции:

static PyObject *module_func(PyObject *self, PyObject *args) {
    /* Ваш код здесь */
    Py_RETURN_NONE;
}

Это Python-функция func внутри модуля module. Указатели на ваши C-функции помещаются в таблицу методов модуля, которая обычно идет дальше в исходном коде.

Таблица сопоставления методов

Эта таблица — простой массив структур PyMethodDef. Структура выглядит так:

struct PyMethodDef {
    char *ml_name;
    PyCFunction ml_meth;
    int ml_flags;
    char *ml_doc;
};

Описание членов:

Таблица должна заканчиваться записью-стражем (sentinel), состоящей из NULL и 0 для соответствующих членов.

Пример

Для вышеуказанной функции таблица методов:

static PyMethodDef module_methods[] = {
    {"func", (PyCFunction)module_func, METH_NOARGS, NULL},
    {NULL, NULL, 0, NULL}
};

Функция инициализации

Последняя часть модуля расширения — функция инициализации. Она вызывается Python-интерпретатором при загрузке модуля. Функция должна называться initModule, где Module — имя модуля.

Функция инициализации должна быть экспортирована из библиотеки, которую вы будете строить. Заголовочные файлы Python определяют PyMODINIT_FUNC для включения соответствующих инструкций для конкретной среды компиляции. Нужно просто использовать её при определении функции.

Общая структура C-функции инициализации:

PyMODINIT_FUNC initModule() {
    Py_InitModule3("func", module_methods, "docstring...");
}

Описание Py_InitModule3:

Собрав всё вместе:

#include 
static PyObject *module_func(PyObject *self, PyObject *args) {
    /* Ваш код */
    Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
    {"func", (PyCFunction)module_func, METH_NOARGS, NULL},
    {NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC initModule() {
    Py_InitModule3("func", module_methods, "docstring...");
}

Пример: простое расширение "helloworld"

#include 
static PyObject* helloworld(PyObject* self) {
    return Py_BuildValue("s", "Hello, Python extensions!!");
}
static char helloworld_docs[] = "helloworld(): Any message you want to put here!!\n";
static PyMethodDef helloworld_funcs[] = {
    {"helloworld", (PyCFunction)helloworld, METH_NOARGS, helloworld_docs},
    {NULL}
};
void inithelloworld(void) {
    Py_InitModule3("helloworld", helloworld_funcs, "Extension module example!");
}

Здесь функция Py_BuildValue используется для построения Python-значения. Сохраните код в hello.c. Далее увидим, как скомпилировать и установить модуль для вызова из Python-скрипта.

Сборка и установка расширений

Пакет distutils упрощает распространение Python-модулей (как чистых Python, так и расширений) стандартным способом. Модули распространяются в исходной форме и собираются/устанавливаются через скрипт настройки, обычно называемый setup.py.

Для вышеуказанного модуля нужен следующий setup.py:

from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',
      ext_modules=[Extension('helloworld', ['hello.c'])])

Теперь выполните команду, которая выполнит все необходимые шаги компиляции и линковки с правильным компилятором, линкером, командами и флагами, и скопирует полученную динамическую библиотеку в соответствующую директорию:

$ python setup.py install

На Unix-системах, скорее всего, нужно запускать эту команду от root для прав записи в директорию site-packages. На Windows это обычно не проблема.

Импорт расширений

После установки расширения вы сможете импортировать и вызывать его в Python-скрипте:

#!/usr/bin/python
import helloworld
print(helloworld.helloworld())

Результат:

Hello, Python extensions!!

Передача параметров функциям

Скорее всего, вы захотите определять функции, принимающие аргументы. Можно использовать другие сигнатуры для C-функций. Например, функция, принимающая некоторое количество параметров:

static PyObject *module_func(PyObject *self, PyObject *args) {
    /* Разбор args и интересные действия */
    Py_RETURN_NONE;
}

Таблица методов с записью для новой функции:

static PyMethodDef module_methods[] = {
    {"func", (PyCFunction)module_func, METH_NOARGS, NULL},
    {"func", module_func, METH_VARARGS, NULL},
    {NULL, NULL, 0, NULL}
};

Для извлечения аргументов из единственного указателя PyObject, переданного в вашу C-функцию, можно использовать API-функцию PyArg_ParseTuple.

Первый аргумент PyArg_ParseTuple — это аргумент args. Это объект, который вы будете «разбирать». Второй аргумент — строка формата, описывающая аргументы так, как вы их ожидаете. Каждый аргумент представлен одним или несколькими символами в строке формата.

static PyObject *module_func(PyObject *self, PyObject *args) {
    int i;
    double d;
    char *s;
    if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
        return NULL;
    }
    /* Интересные действия */
    Py_RETURN_NONE;
}

Компиляция новой версии модуля и импорт позволяют вызывать новую функцию с любым количеством аргументов любого типа:

module.func(1, s="three", d=2.0)
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)

Функция PyArg_ParseTuple

Стандартная сигнатура:

int PyArg_ParseTuple(PyObject* tuple, char* format, ...)

Возвращает 0 при ошибках, значение ≠ 0 при успехе. tuple — это PyObject*, второй аргумент C-функции. format — C-строка, описывающая обязательные и опциональные аргументы.

Список кодов формата для PyArg_ParseTuple:

КодC-типЗначение
ccharPython-строка длины 1 становится C char.
ddoublePython float становится C double.
ffloatPython float становится C float.
iintPython int становится C int.
llongPython int становится C long.
Llong longPython int становится C long long.
OPyObject*Получает не-NULL заимствованную ссылку на Python-аргумент.
schar*Python-строка без встроенных null в C char*.
s#char*+intЛюбая Python-строка в C адрес и длину.
t#char*+intRead-only single-segment buffer в C адрес и длину.
uPy_UNICODE*Python Unicode без встроенных null в C.
u#Py_UNICODE*+intЛюбой Python Unicode в C адрес и длину.
w#char*+intRead/write single-segment buffer в C адрес и длину.
zchar*Как s, также принимает None (устанавливает C char* в NULL).
z#char*+intКак s#, также принимает None.
(...)as per ...Python-последовательность считается одним аргументом на элемент.
|Следующие аргументы опциональны.
:Конец формата, за которым следует имя функции для сообщений об ошибках.
;Конец формата, за которым следует полный текст сообщения об ошибке.

Возврат значений

Py_BuildValue принимает строку формата, как PyArg_ParseTuple. Вместо адресов значений, которые вы строите, передаете сами значения. Пример реализации функции сложения:

static PyObject *foo_add(PyObject *self, PyObject *args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return NULL;
    }
    return Py_BuildValue("i", a + b);
}

На Python это выглядело бы так:

def add(a, b):
    return (a + b)

Можно вернуть два значения из функции — в Python они будут захвачены списком:

static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
        return NULL;
    }
    return Py_BuildValue("ii", a + b, a - b);
}

На Python:

def add_subtract(a, b):
    return (a + b, a - b)

Функция Py_BuildValue

Стандартная сигнатура:

PyObject* Py_BuildValue(char* format, ...)

format — C-строка, описывающая Python-объект для построения. Последующие аргументы — C-значения, из которых строится результат. Результат PyObject* — новая ссылка.

Часто используемые коды строк:

КодC-типЗначение
ccharC char становится Python-строкой длины 1.
ddoubleC double становится Python float.
ffloatC float становится Python float.
iintC int становится Python int.
llongC long становится Python int.
NPyObject*Передает Python-объект и крадет ссылку.
OPyObject*Передает Python-объект и INCREF как обычно.
O&convert+void*Произвольное преобразование.
schar*C 0-terminated char* в Python-строку, или NULL в None.
s#char*+intC char* и длина в Python-строку, или NULL в None.
uPy_UNICODE*C-wide null-terminated строка в Python Unicode, или NULL в None.
u#Py_UNICODE*+intC-wide строка и длина в Python Unicode, или NULL в None.
w#char*+intRead/write single-segment buffer в C адрес и длину.
zchar*Как s, также принимает None.
z#char*+intКак s#, также принимает None.
(...)as per ...Строит Python-кортеж из C-значений.
[...]as per ...Строит Python-список из C-значений.
{...}as per ...Строит Python-словарь из C-значений, чередуя ключи и значения.

Код {...} строит словари из четного числа C-значений, чередуя ключи и значения. Например, Py_BuildValue("{issi}", 23, "zig", "zag", 42) возвращает словарь как Python'овский {23: 'zig', 'zag': 42}.

Python

  1. Объектно-ориентированное программирование в Python: классы, наследование, инкапсуляция и полиморфизм
  2. Функция print() в Python: практическое руководство с примерами
  3. Мастерство метода strip() в Python: подробное руководство и практические примеры
  4. Мастерство метода str.count() в Python: как подсчитывать символы и подстроки с примерами
  5. Мастерство строк форматирования format() в Python: понятные примеры
  6. Мастерство метода String.find() в Python: синтаксис, примеры и альтернативы
  7. Освоение лямбда-функций Python: практические примеры и лучшие практики
  8. Функция round() в Python: подробное объяснение с практическими примерами
  9. Освоение функции map() в Python: синтаксис, примеры и лучшие практики
  10. Python timeit(): измерение времени выполнения с практическими примерами