
cython позволяет писать программы, выглядящие почти как питонские, но с добавлением статических деклараций типов. Эти программы (foo.pyx) транслируются в исходные тексты на C (foo.c) и затем компилируются. Определённые в них функции могут использоваться из программ на чистом питоне. Программа на cython-е может также вызывать функции из библиотек, написанных на C. cython не пытается автоматически сгенерировать интерфейсы к таким библиотекам, читая их .h файлы; для этого можно использовать swig или другие подобные системы.
В ipython можно писать cython фрагменты inline, если загрузить расширение cython.
%load_ext cython
Это интерпретируемая функция на питоне.
def fib(n):
    if n<=2:
        return 1
    a,b=1,1
    for i in range(n-2):
        a,b=b,a+b
    return b
fib(90)
%timeit fib(90)
Это такая же функция на cython, типы переменных не объявлены - то есть все они обычные питонские объекты.
%%cython
def dyn_fib(n):
    if n<=2:
        return 1
    a,b=1,1
    for i in range(n-2):
        a,b=b,a+b
    return b
dyn_fib(90)
%timeit dyn_fib(90)
Получилось чуть быстрее. Скомпилированная программа выполняет всю ту же возню с типами и их преобразованиями, что и интерпретируемая.
Теперь типы декларированы статически.
%%cython
def stat_fib(long n):
    cdef long i,a,b
    if n<=2:
        return 1
    a,b=1,1
    for i in range(n-2):
        a,b=b,a+b
    return b
stat_fib(90)
%timeit stat_fib(90)
Получилось на порядок быстрее.
c_fib - это фактически функция на C, только написанная в cython-ском синтаксисе. Её можно вызывать откуда угодно в той же программе на cython, но не из питонской программы. Поэтому напишем обёртку, которую можно вызывать из питона.
%%cython
cdef long c_fib(long n):
    cdef long i,a,b
    if n<=2:
        return 1
    a,b=1,1
    for i in range(n-2):
        a,b=b,a+b
    return b
def wrap_fib(long n):
    return c_fib(n)
wrap_fib(90)
%timeit wrap_fib(90)
Время то же самое.
cpdef создаёт как C функцию, так и питонскую. Первая вызывается из cython, вторая из питона.
%%cython
cpdef long cp_fib(long n):
    cdef long i,a,b
    if n<=2:
        return 1
    a,b=1,1
    for i in range(n-2):
        a,b=b,a+b
    return b
cp_fib(90)
%timeit cp_fib(90)
!cat cfib.c
!cat cfib.h
Скомпилируем его.
!gcc -fPIC -c cfib.c
Напишем обёртку на cython.
!cat wrap.pyx
Скомпилируем её и соберём в библиотеку.
%%!
cython -3 wrap.pyx
CFLAGS=$(python-config --cflags)
LDFLAGS=$(python-config --ldflags)
gcc $CFLAGS -fPIC -c wrap.c
gcc $LDFLAGS -shared wrap.o cfib.o -o wrap.so
Эту библиотеку можно импортировать в программу на питоне.
from wrap import fib
fib(90)
%timeit fib(90)
Пулучилось чуть быстрее, чем функция на cython.
Структуры можно описывать в cython с помощью ctypedef struct. Поля в них описываются фактически в синтаксисе C. Переменную, описываемую в cdef, можно, если хочется, сразу инициализировать. Имя типа-структуры можно использовать как функцию, аргументы которой - её поля (в порядке описания). print печатает структуру как словарь; на самом деле это не словарь, а структура языка C, не содержащая накладных расходов по памяти и времени, имеющихся у словаря, но и не дающая гибкости словаря. Поля структуры обозначаются z.re; их можно менять.
В cython можно работать с указателями. Импортируем malloc и free из стандартной библиотеки. Результат malloc - адрес, его нужно привести к правильному типу, используя <type>. В C поля структуры, на которую ссылается w, обозначаются w->re; в cython - просто w.re. В C структура, на которую ссылается w, обозначается *w; в cython такой синтаксис не разрешён, вместо этого надо писать w[0] (в C это тоже законная форма записи, но чаще используется *w). При работе с указателями управление памятью производится вручную, а не автоматически, как в питоне, так что не забывайте free.
%%cython
ctypedef struct mycomplex:
    double re
    double im
cdef mycomplex z=mycomplex(1.,2.)
print(z)
print(z.re)
z.re=-1
print(z)
# pointers
from libc.stdlib cimport malloc,free
cdef mycomplex *w=<mycomplex*>malloc(sizeof(mycomplex))
w.re,w.im=2.,1.
print(w[0])
free(w)
cython позволяет определять классы, объекты которых являются фактически структурами языка C. Их атрибуты нужно статически описывать с помощью cdef; во время выполнения нельзя добавлять новые атрибуты (или уничножать имеющиеся). Вот пример такого класса. Его основной метод atol вызывает функцию atol из стандартной библиотеки C, преобразующую строку в long.
!cat C1.pyx
Есть удобный способ импортировать pyx модуль в питон: pyximport, он автоматически произведёт преобразование в C, компиляцию и сборку.
import pyximport
pyximport.install()
from C1 import C1
o=C1()
s=b"12345"
o.set_s(s)
o.atol()
print(o.get_n())
Тип char* в C соответствует типу bytes в питоне. При совместном использовании питона с его автоматическим управлением памятью и C с указателями нужно соблюдать осторожность. Строка b"12345" доступна в питоне как значение переменной s, поэтому занимаемая ей память не будет освобождена, пока s не будет присвоено другое значение. Мы скопировали её адрес в атрибут o.s типа char*. Если бы мы не присвоили эту строку переменной s, а прямо подставили бы её в качестве аргумента метода o.set_s, то питон не знал бы, что её надо сохранять, и освободил бы занимаемую её память. Указатель o.s указывал бы после этого неведомо куда, с катастрофическими последствиями.
Усовершенствуем немного эту cython программу. По умолчанию cdef атрибуты недоступны ни из питона, ни из cython программы. Но можно описать их как public или readonly, тогда не нужны будут методы get_foo и set_foo. Метод __init__ может быть и не будет вызван (например, другой класс унаследовал текущий, и его __init__ не вызвал __init__ родителя; если в структуре есть указатели, то они могут остаться неинициализированными. Поэтому лучше использовать __cinit__, который обязательно вызывается сразу после выделения память для объекта.
!cat C2.pyx
from C2 import C2
o=C2()
o.s=s
o.atol()
print(o.n)
cdef классы поддерживают наследование (только от одного класса, не множественное). Можно написать класс-потомок как cdef класс на cython. Можно и написать класс-потомок на питоне. Пусть мы хотим добавить к нашему классу метод преобразования строки в число с плавающей точкой, но нам лень использовать atof из стандортной библиотеки C. Сделаем это обычными средствами питона. Атрибут x добавляется к объектам класса C3 динамически, описывать его не надо.
class C3(C2):
    
    def atof(self):
        self.x=float(self.s)
o=C3()
s=b"12345.6789"
o.s=s
o.atof()
print(o.x)
Рассмотрим очень упощённый пример того, как можно написать удобный питонский интерфейс к библиотеке на C, используя cython. Если бы мы хотели использовать эту библиотеку из программы на C, достаточно было бы включить #include "foo.h" в эту программу.
!cat cfoo.h
Здесь описан тип-структура CFoo. Функция Foo_new создаёт и инициализирует такую структуру и возвращает указатель на неё. Функция Foo_del уничтожает эту структуру. Наконец, функция Foo_f делает какое-то вычисление со своим параметром y и данными из структуры. Подобным образом часто выглядят интерфейсы к генераторам случайных чисел: мы можем создать несколько структур с начальными данными и получить несколько независимых потоков случайных чисел.
А вот реализация на C.
!cat cfoo.c
В первую очередь мы напишем файл определений cython. Он почти копирует foo.h с минимальными синтаксическими изменениями.
!cat foo.pxd
Теперь напишем удобную объектно-ориентированную обёртку. Файл определений импортируется при помощи cimport (мы уже использовали эту команду, когда импортировали libc.stdlib; cython содержит ряд стандартных pxd файлов, включая stdlib.pxd, stdio.pxd и т.д.). Теперь определим cdef класс Foo. Метод __dealloc__ вызывается в последний момент перед уничтожением объекта (условие if self.foo!=NULL: написано из перестраховки, в законном объекте класса Foo этот атрибут всегда не NULL, т.к. он инициализируется в __cinit__).
!cat foo.pyx
Скомпилируем и соберём.
%%!
gcc -fPIC -c cfoo.c
cython -3 foo.pyx
CFLAGS=$(python-config --cflags)
LDFLAGS=$(python-config --ldflags)
gcc $CFLAGS -fPIC -c foo.c
gcc $LDFLAGS -shared foo.o cfoo.o -o foo.so
from foo import Foo
Теперь мы можем в питоне создавать объекты класса Foo и вызывать их метод f.
o=Foo(2,0.)
o.f(3.)