Python - Программирование расширений с помощью C

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

Модуль расширения Python - это не что иное, как обычная библиотека C. На машинах Unix эти библиотеки обычно заканчиваются на.so(для общего объекта). На компьютерах с Windows вы обычно видите.dll (для динамически подключаемой библиотеки).

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

Чтобы начать писать расширение, вам потребуются файлы заголовков Python.

  • На машинах Unix для этого обычно требуется установка пакета для конкретного разработчика, такого как python2.5-dev .

  • Пользователи Windows получают эти заголовки как часть пакета при использовании двоичного установщика Python.

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

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

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

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

  • Функции C, которые вы хотите предоставить в качестве интерфейса вашего модуля.

  • Таблица, отображающая имена ваших функций, как их видят разработчики Python, с функциями C внутри модуля расширения.

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

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

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

Не забудьте включить 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-эквивалент PythonNoneзначение. Заголовки Python определяют макрос Py_RETURN_NONE, который делает это за нас.

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

Ваши функции C обычно именуются путем объединения имен модуля Python и функций вместе, как показано здесь -

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   Py_RETURN_NONE;
}

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

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

Эта таблица методов представляет собой простой массив структур PyMethodDef. Эта структура выглядит примерно так -

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

Вот описание членов этой структуры -

  • ml_name - Это имя функции, которое интерпретатор Python представляет, когда он используется в программах Python.

  • ml_meth - Это должен быть адрес функции, которая имеет одну из сигнатур, описанных в предыдущем разделе.

  • ml_flags - Это сообщает интерпретатору, какую из трех сигнатур использует ml_meth.

    • Этот флаг обычно имеет значение METH_VARARGS.

    • Этот флаг можно объединить побитовым ИЛИ с помощью METH_KEYWORDS, если вы хотите разрешить аргументы ключевого слова в вашей функции.

    • Это также может иметь значение METH_NOARGS, которое указывает, что вы не хотите принимать какие-либо аргументы.

  • ml_doc - Это строка документации для функции, которая может иметь значение NULL, если вам не хочется ее писать.

Эта таблица должна быть завершена сигналом, который состоит из значений 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 -

  • func - Это функция, которую нужно экспортировать.

  • module_methods - Это имя таблицы сопоставления, определенное выше.

  • docstring - Это комментарий, который вы хотите оставить в своем расширении.

Объединение всего этого выглядит следующим образом -

#include <Python.h>

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   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...");
}

пример

Простой пример, в котором используются все вышеперечисленные концепции -

#include <Python.h>

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, чтобы иметь права на запись в каталог пакетов сайта. Обычно это не проблема Windows.

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

После установки расширения вы сможете импортировать и вызывать это расширение в своем скрипте Python следующим образом:

#!/usr/bin/python
import helloworld

print helloworld.helloworld()

Это даст следующий результат -

Hello, Python extensions!!

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

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

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Parse args and do something interesting here. */
   Py_RETURN_NONE;
}

Таблица методов, содержащая запись для новой функции, будет выглядеть так:

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

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

Первым аргументом 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;
   }
   
   /* Do something interesting here. */
   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 Функция

Вот стандартная подпись для PyArg_ParseTuple функция -

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

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

Вот список кодов формата для PyArg_ParseTuple функция -

Код Тип C Имея в виду
c char Строка Python длиной 1 становится символом C.
d двойной Поплавок Python становится двойным C.
ж плавать Поплавок Python становится поплавком C.
я int Python int становится C int.
л долго Python int превращается в длинный C.
L долго долго Python int становится C long long
О PyObject * Получает заимствованную ссылку на аргумент Python, отличную от NULL.
s символ * Строка Python без встроенных нулей в C char *.
s # char * + int Любая строка Python с адресом C и длиной.
т # char * + int Односегментный буфер только для чтения с адресом и длиной C.
ты Py_UNICODE * Python Unicode без встроенных нулей в C.
ты # Py_UNICODE * + int Любой адрес и длина Python Unicode C.
w # char * + int Чтение / запись односегментного буфера по адресу C и длине.
z символ * Как и s, также принимает None (устанавливает C char * в NULL).
z # char * + int Как и s #, также принимает None (устанавливает C char * в NULL).
(...) согласно ... Последовательность Python рассматривается как один аргумент для каждого элемента.
|   Следующие аргументы необязательны.
:   Конец формата, за которым следует имя функции для сообщений об ошибках.
;   Конец формата, за которым следует весь текст сообщения об ошибке.

Возвращаемые значения

Py_BuildValue принимает строку формата так же, как PyArg_ParseTuple . Вместо того, чтобы передавать адреса создаваемых вами значений, вы передаете фактические значения. Вот пример, показывающий, как реализовать функцию добавления -

static PyObject *foo_add(PyObject *self, PyObject *args) {
   int a;
   int 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;
   int 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 Функция

Вот стандартная подпись для Py_BuildValue функция -

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

Здесь формат - это строка C, описывающая создаваемый объект Python. Следующие аргументы Py_BuildValue - это значения C, на основе которых строится результат. Результат PyObject * - это новая ссылка.

В следующей таблице перечислены часто используемые строки кода, из которых ноль или более объединены в строковый формат.

Код Тип C Имея в виду
c char AC char становится строкой Python длиной 1.
d двойной AC double становится плавающей точкой Python.
ж плавать AC float становится Python float.
я int AC int становится Python int.
л долго AC long становится Python int.
N PyObject * Передает объект Python и крадет ссылку.
О PyObject * Передает объект Python и выполняет INCREF как обычно.
O & convert + void * Произвольное преобразование
s символ * C символом * в конце строки Python или NULL в None.
s # char * + int C char * и длина в строку Python или от NULL до None.
ты Py_UNICODE * Строка с нулевым символом в конце для Python Unicode или NULL в None.
ты # Py_UNICODE * + int Строка шириной C и длина в Python Unicode или от NULL до None.
w # char * + int Чтение / запись односегментного буфера по адресу C и длине.
z символ * Как и s, также принимает None (устанавливает C char * в NULL).
z # char * + int Как и s #, также принимает None (устанавливает C char * в NULL).
(...) согласно ... Строит кортеж Python из значений C.
[...] согласно ... Строит список Python из значений C.
{...} согласно ... Строит словарь Python из значений C, чередующихся ключей и значений.

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