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.)