Подключение C-кода в Python с помощью Distutils

В предыдущих постах описаны примеры использования С-библиотек в Python с помощью Cython. Там все было здорово, все работало быстро и без проблем. Единственное, в том случае требуется установить Cython и изучить дополнительно его документацию.

Для быстрого достижения цели, Cython прекрасно себя проявил. Но что делать, если хочется вызывать C-код без установки дополнительных пакетов? Для этого есть несколько возможностей и одна из них, использование distutils — стандартная Python-утилита для сборки и установки пакетов.

Исходные данные:

  • Linux
  • Python 3.5
  • Ipython (Jupyter Notebook)
  • Файл с кодом на C

Создадим директорию, необходимые файлы и новый блокнот:

  • c.c — файл с вызываемой функцией
  • c.h — заголовочный файл
  • cwrap.c — обертка для подключения к Python
  • c.ipynb — блокнот

Код на С

Создадим отдельный файл с функцией.

#include <stdlib.h>
#include <stdio.h>

char* inside_differ(const char* s)
{
	int required_size = 100;
	/* Reserve memory for return variable */
	char *r = (char*)malloc(sizeof (char) * required_size); 

	/* Change string */
	sprintf(r, "we get in differ: %s", s);

	return r;
}

файл c.c, код на C

Создадим заголовочный файл для порядка.

// header /c.h/
char* inside_differ(const char*);

заголовочный файл c.h, код на C

Создадим обертку, где и будем принимать вызов из Python, обрабатывать его и отправлять обратно.

C-function — функция на чистом C, куда передаем строку, меняем ее и возвращаем.
Enter point — функция, где обрабатываем аргументы из Python и вызываем нашу функцию на C.
Array with methods —  массив, где перечисляются все реализованные методы доступные в Python.
Struct — массив с описанием модуля для создания в Python.
Init — функция инициализации модуля в Python.

#include <Python.h>

#include <stdlib.h>
#include <stdio.h>

#include "c.h"

/* C-function */
static char* inside_c(const char* s)
{
	int required_size = 100;
	/* Reserve memory for return variable */
	char *r = (char*)malloc(sizeof (char) * required_size); 

	/* Change string */
	sprintf(r, "we get: %s", s);

	return r;
}

/*
 * Enter point from Python
 */
static PyObject*
py_inside(PyObject* self, PyObject* args)
{
    const char* s;

    /* Parse arguments from Python:
       "s" - type string
       &s  - to variable s
     */
    if (!PyArg_ParseTuple(args, "s", &s))
        return NULL;

    /* Return tuple with strings
     */
    return Py_BuildValue("ss", inside_c(s), inside_differ(s));
}

/* Array with methods
 */
static PyMethodDef module_methods[] =
{
     /* name from python, name in C-file, ..., __doc__ string of method */
     {"inside", py_inside, METH_VARARGS, "Say hello from C."},
     {NULL, NULL, 0, NULL}
};

/* Struct with info about module to create it in Python
 */
static struct PyModuleDef c_call =
{
    PyModuleDef_HEAD_INIT,
    "c_call",               /* name of module */
    "Test calling from C",  /* module documentation, may be NULL */
    -1,                     /* size of per-interpreter state of the module,
                               or -1 if the module keeps state in global variables. */
    module_methods
};

/* Init our module in Python
 */
PyMODINIT_FUNC PyInit_c_call(void)
{
    return PyModule_Create(&c_call);
}

файл cwrap.c, код на C

IPython

Теперь все действия в блокноте Ipython, каждый кусок кода вставляем в отдельные ячейки.

Создаем python-скрипт setup_c.py для компиляции библиотеки в python-пакет.

%%writefile setup_c.py
from distutils.core import setup, Extension
ext = Extension('c_call', sources = ['cwrap.c', 'c.c'])
setup(name="C Call", ext_modules = [ext])

ipython, ячейка 1

Запускаем созданный на предыдущем шаге файл setup_c.py для компиляции в текущую директорию C-библиотеки.

%%bash
python setup_c.py build_ext --inplace

ipython, ячейка 2

Подключаем модуль, показываем описания модуля и функции. Тестируем.

import c_call  # import our c-library

print(c_call.__doc__, ", ", c_call.inside.__doc__)
income, income2 = c_call.inside("from python")  # call and get tuple with strings
income, income2

ipython, ячейка 3

Репозиторий с кодом.