C/Python API

C/Python API

Работает с объектами

Пишем специальный C-код для работы с питоном.

Все объекты Python представляются как структуры PyObject и заголовочный файл Python.h предоставляет различные функции для работы с объектами. Например, если PyObject одновременно PyListType (список), то мы можем использовать функцию PyList_Size(), чтобы получить длину списка. Это эквивалентно коду len(some_list) в Python. Большинство основных функций/операторов для стандартных Python объектов доступны в C через Python.h.

Пример

Давайте напишем С-библиотеку для суммирования всех элементов списка Python (все элементы являются числами). Начнем с интерфейса, который мы хотим иметь в итоге. Вот Python-файл, использующий пока отсутствующую C-библиотеку:

# Это не простой Python import addList это C-библиотека
import addList

l = [1,2,3,4,5]
print("Сумма элементов списка - " + str(l) + " = " +  str(addList.add(l)))

С-код adder.c:

// Python.h содержит все необходимые функции, для работы с объектами Python
#include <Python.h>

// Эту функцию мы вызываем из Python кода
static PyObject* addList_add(PyObject* self, PyObject* args){

  PyObject * listObj;

  // Входящие аргументы находятся в кортеже
  // В нашем случае есть только один аргумент - список, на который мы будем
  // ссылаться как listObj
  if (! PyArg_ParseTuple( args, "O", &listObj))
    return NULL;

  // Длина списка
  long length = PyList_Size(listObj);

  // Проходимся по всем элементам
  int i, sum =0;
  for(i = 0; i < length; i++){
    // Получаем элемент из списка - он также Python-объект
    PyObject* temp = PyList_GetItem(listObj, i);
    // Мы знаем, что элемент это целое число - приводим его к типу C long
    long elem = PyInt_AsLong(temp);
    sum += elem;
  }

  // Возвращаемое в Python-код значение также Python-объект
  // Приводим C long к Python integer
  return Py_BuildValue("i", sum);
}

// Немного документации для 'add'
static char addList_docs[] =
    "add( ): add all elements of the list\n";

/*
Эта таблица содержит необходимую информацию о функциях модуля
<имя функции в модуле Python>, <фактическая функция>,
<ожидаемые типы аргументов функции>, <документация функции>
*/
static PyMethodDef addList_funcs[] = {
    {"add", (PyCFunction)addList_add, METH_VARARGS, addList_docs},
    {NULL, NULL, 0, NULL}
};

/*
addList имя модуля и это блок его инициализации.
<желаемое имя модуля>, <таблица информации>, <документация модуля>
*/
PyMODINIT_FUNC initaddList(void){
    Py_InitModule3("addList", addList_funcs,
                   "Add all the lists");
}
  • Заголовочный файл Python.h содержит все требуемые типы (для представления типов объектов в Python) и определения функций (для работы с Python-объектами).
  • Дальше мы пишем функцию, которую собираемся вызывать из Python. По соглашению, имя функции принимается {module-name}_{function-name}, которое в нашем случае - addList_add. Подробнее об этой функции будет дальше.
  • Затем заполняем таблицу, которая содержит всю необходимую информацию о функциях, которые мы хотим иметь в модуле. Каждая строка относится к функции, последняя - контрольное значение (строка из null элементов). Затем идёт блок инициализации модуля - PyMODINIT_FUNC init{module-name}.

Функция addList_add принимает аргументы типа PyObject (args также является кортежем, но поскольку в Python всё является объектами, мы используем унифицированный тип PyObject). Мы парсим входные аргументы (фактически, разбиваем кортеж на отдельные элементы) при помощи PyArg_ParseTuple(). Первый параметр является аргументом для парсинга. Второй аргумент - строка, регламентирующая процесс парсинга элементов кортежа args. Знак на N-ой позиции строки сообщает нам тип N-ого элемента кортежа args, например - 'i' значит integer, 's' - строка и 'O' - Python-объект. Затем следует несколько аргументов, где мы хотели бы хранить выходные элементы PyArg_ParseTuple(). Число этих аргументов равно числу аргументов, которые планируется передавать в функцию модуля и их позиционность должна соблюдаться. Например, если мы ожидаем строку, целое число и список в таком порядке, сигнатура функции будет следующего вида:

int n;
char *s;
PyObject* list;
PyArg_ParseTuple(args, "isO", &n, &s, &list);

В данном случае, нам нужно извлечь только объект списка и сохранить его в переменной listObj. Затем мы используем функцию PyList_Size() чтобы получить длину списка. Логика совпадает с len(some_list) в Python.

Теперь мы итерируем по списку, получая элементы при помощи функции PyLint_GetItem(list, index). Так мы получаем PyObject*. Однако, поскольку мы знаем, что Python-объекты еще и PyIntType, то используем функцию PyInt_AsLong(PyObj *) для получения значения. Выполняем процедуру для каждого элемента и получаем сумму. Сумма преобразуется в Python-объект и возвращается в Python-код при помощи Py_BuildValue(). Аргумент "i" означает, что возвращаемое значение имеет тип integer.

В заключение мы собираем C-модуль. Сохраните следующий код как файл setup.py:

# Собираем модули

from distutils.core import setup, Extension

setup(name='addList', version='1.0',\
      ext_modules=[Extension('addList', ['adder.c'])])

Запускаем:

python setup.py install

Это соберёт и установит C-файл в Python-модуль, который нам требуется.

Теперь осталось только протестировать работоспособность:

# Модуль, вызывающий C-код
import addList

l = [1,2,3,4,5]
print("Сумма элементов списка - " + str(l) + " = " +  str(addList.add(l)))

запускаем:

Сумма элементов списка - [1, 2, 3, 4, 5] = 15

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

results matching ""

    No results matching ""