IPython: вызов библиотеки C из Python с помощью Cython

IPython безусловно очень удобная штука. Быстрая отладка идеи на Python, стабильная и быстрая работа, куча плагинов, делающих эту оболочку просто незаменимой.

Однако в этот раз мне необходимо использовать библиотеку написанную на языке C. А хочу я все это делать в IPython. Конечно, кроме написания C-кода.

Все будет работать в связке с Cython. Язык Cython был создан для получения python-пакетов, работающих также быстро, как-будто они написаны на C/С++ с возможностью написания кода в стиле схожем с Python. Бонусом идет удобная и быстрая возможность обертки C/C++-библиотек для Python.

Примеры реализованы под Linux. При повторении данного примера под Windows необходимо будет сделать предварительную подготовку, установить компилятор MinGW и настроить все пути.

Первым делом определяем директорию и создаем новый блокнот IPython. В эту директорию кладем файл c.c с кодом на C.

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

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

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

    return r;
}

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

Далее будем работать в блокноте Ipython и все куски кода вставляем в отдельные ячейки. Включаем cython-магию в ipython.

 %cython

ipython, ячейка 1

Создаем файл c_call.pyx с заголовком функции из C.

%%writefile c_call.pyx
cdef extern from "c.c":
    cpdef char* inside_c(char*)

ipython, ячейка 2

Создаем python-скрипт setup.py для компиляции библиотеки и заголовочного файла в python-пакет.

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

ipython, ячейка 3

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

%%bash
python setup.py build_ext --inplace

ipython, ячейка 4

Запускаем получившийся винегрет и наслаждаемся.

import c_call # import pyx-file which has our c-library
income = c_call.inside_c(b"from python") # send bytes, not string
income # get bytes (char*)

ipython, ячейка 5

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