Cython

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 21:34, 8 июня 2016.
Cython
fraimed
Парадигма язык программирования
Спроектировано Robert Bradshaw, Stefan Behnel и др.
OS кроссплатформенный
Лицензия Apache License
Портал: http://cython.org

Cython — (произносится [ ˈcaɪ.θən ])язык программирования, упрощающий написание модулей С/С++ кода для Python. Кроме стандартного синтаксиса Python, поддерживаются:

  • прямой вызов функций и методов С/С++ из кода на Cython;
  • строгая типизация переменных, классов, атрибутов классов.

Код Cython преобразуется в С/С++ код для последующей компиляции и впоследствии может использоваться как расширение стандартного Python или как независимое приложение со встроенной библиотекой выполнения Cython.

История

Cython является производным от языка Pyrex, но имееет больше возможностей и лучшую оптимизацию, чем Pyrex. Cython отделился от Pyrex в 2007 году разработчиками пакета компьютерной алгебры Sage, потому что они были недовольны ограничениями Pyrex. Сначала проект получил название SageX. Когда разработчики обнаружили, что люди скачивали Sage только для того, чтобы получить SageX, SageX был выделен в отдельный от Sage проект и объеденен с Cython-LXML, после чего получил название Cython.

Описание

Суть природы Cython’а можно выразить в следующих словах: Cython — это Python с типами данных языка C. Cython — это Python, в том смысле, что почти любой код на Питоне будет валидным с точки зрения Cython (и хотя есть некоторые ограничения, но в первом приближении это так). Компилятор Cython преобразует его в код на C, который будет делать эквивалентные вызовы к Python/C API. Однако Cython больше чем просто Python, поскольку параметрам и переменным можно задавать типы языка C. В коде можно параллельно использовать данные Python-типов и C-типов, при этом преобразование осуществляется автоматически, где это возможно. Кроме того, ведется подсчет ссылок и проверка ошибок в Python-операциях, то есть вам доступна вся мощь обработки ошибок Питона, в том числе конструкции try-except и try-finally, — даже в ходе обработки данных C-типов.

Основы языка

Определения переменных и типов

Инструкция cdef объявляет переменные C-типов (как локальные, так и на уровне модуля):

cdef int i, j, k
cdef float f, g[42], *h

а также C-типы struct, union или enum:

cdef struct Grail:
    int age
    float volume

cdef union Food:
    char *spam
    float *eggs

cdef enum CheeseType:
    cheddar, edam,
    camembert

cdef enum CheeseState:
    hard = 1
    soft = 2
    runny = 3

Функции

Cython позволяет определить функцию одним из двух способов:

  • Python-функции определяются с помощью инструкции def, как и в Питоне. Они принимают в качестве параметров Python-объекты и возвращают тоже Python-объекты.
  • C-функции определяются при помощи новой инструкции cdef. Они могут принимать и возвращать как Python-объекты, так и значения C-типов.

В пределах одного Cython-модуля функции разного типа могут свободно вызывать друг друга, но извне для импорта и использования доступны только Python-функции. То есть, если вы хотите «экспортировать» функцию из Cython-модуля, ее следует объявить как Python-функцию с помощью инструкции def. Существует также гибридный вариант под названием cpdef, такие функции доступны отовсюду, но используют более быстрые соглашения запуска при вызове из Cython-кода. При любом способе определения функции, можно объявить тип ее параметров при помощи обычного синтаксиса C. Например:

def spam(int i, char *s):
    ...

cdef int eggs(unsigned long l, float f):

При объявлении типа параметра Python-функции, он передается как Python-объект и автоматически преобразуется в значение C, если это возможно. В настоящий момент автоматическое преобразование доступно только для числовых и строковых типов; попытка использования других типов параметров в Python-функциях вызовет ошибку компиляции. C-функции же могут принимать параметры любого типа, поскольку они передаются непосредственно с использованием стандартного вызова C.

Классы

Для совместимости с объектно-ориентированным подходом, Cython поддерживает обычные классы так же, как и Python:

class MathFunction(object):
    def __init__(self, name, operator):
        self.name = name
        self.operator = operator

    def __call__(self, *operands):
        return self.operator(*operands)

Помимо встроенных типов Питона, Cython позволяет использование еще одного типа классов: extension types, иногда называемого “cdef-классы” по имени ключевого слова, используемого для их объявления. Имея некоторые ограничения по сравнению с обычными Python-классами, они, как правило, используют меньше памяти и процессорного времени. Основное отличие заключается в том, что они хранят свои атрибуты и методы в виде С-объекта struct, а не в Python-словаре. Это позволяет хранить значения произвольных C-типов, не оборачивая их в Python-объекты, и получать прямой доступ к атрибутам и методам на уровне C без необходимости искать их в Python-словаре. Обычные Python-классы могут наследовать от cdef-классов, но не наоборот. Cython должен знать всю иерархию наследования, чтобы подготовить нужные C-struct’ы, и допускает один-единственный класс-родитель. Python-классы же могут наследовать от любого количества классов любого типа (встроенных, дополнительных или обычных), как в коде на Cython, так и в чистом Питоне.. Напишем как пример функцию интегрирования. Для этого создадим cdef-класс для представления функции на числах с плавающей точкой:

cdef class Function:
    cpdef double evaluate(self, double x) except *:
        return 0

cdef class SinOfSquareFunction(Function):
    cpdef double evaluate(self, double x) except *:
        return sin(x**2)

def integrate(Function f, double a, double b, int N):
  cdef int i
  cdef double s, dx
  if f is None:
      raise ValueError("f cannot be None")
  s = 0
  dx = (b-a)/N
  for i in range(N):
      s += f.evaluate(a+i*dx)
  return s * dx

print(integrate(SinOfSquareFunction(), 0, 1, 10000))

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

Автоматическое приведение типов

Если Python-объект используется в контексте, требующем C-значение (или наоборот), то для основных числовых и строковых типов производится автоматическое преобразование.

C-типы Из Python-типов К Python-типам
[unsigned] char, [unsigned] short, int, long int, long int
unsigned int, unsigned long, [unsigned] long long int, long long
float, double, long double int, long, float float
char * str/bytes str/bytes
struct dict

Различия между выражениями C и Cython

Существуют некоторые различия в синтаксисе и семантике C-выражений и Сython-выражений, особенно в части конструкций C, не имеющих прямого аналога в Питоне.

  • Целочисленный литерал рассматривается как C-константа и может быть усечен на усмотрение вашего компилятора C. Чтобы получить питоновский integer (любой точности), сразу преобразуйте его явным образом в Python-объект (например, <object>100000000000000000000). Суффиксы L, LL и U имеют то же значение, что и в C.
  • Оператор -> отсутствует. Вместо p->x пишите p.x.
  • Унарный оператор * отсутствует. Вместо *p используйте p[0].
  • Оператор & сохраняет семантику C.
  • Нулевой указатель называется NULL, а не 0. NULL является зарезервированным словом.
  • Приведение типов записывается в виде <тип>значение, например:
cdef char *p, float *q
p = <char*>q

Примеры кода

Простейшее приложение

Поскольку Cython способен обработать практически любой валидный код на Питоне, то самое сложное, что придется сделать вначале, — всего лишь разобраться, как же компилировать ваше расширение. Итак, возьмем традиционную программу на Питоне:

print "Hello World"

Первым делом переименуем файл в helloworld.pyx. Затем нужно создать файл setup.py, который будет служить чем-то вроде Makefile. Ваш setup.py для начала будет таким:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

setup(
    cmdclass = {'build_ext': build_ext},
    ext_modules = [Extension("helloworld", ["helloworld.pyx"])]
)

Теперь для компиляции Cython-файла запускаем следующую команду:

$ [[python]] setup.py build_ext --inplace

В итоге, в локальной папке вы получите файл под названием helloworld.so (в Unix-системе) либо helloworld.dll (в Windows). Пользоваться этим файлом просто: запускаем интерпретатор Питона и импортируем как обычный модуль:

>>> import helloworld
Hello World

Поздравляем! Теперь вы умеете компилировать модули, написанные на Cython. Правда, пока что этот пример не особо вдохновляет на его использование, так что попробуем что-нибудь более приближенное к реальности.

Простые числа

Теперь на примере небольшой программы primes.pyx для нахождения простых чисел покажем некоторые возможности Cython’а. Программа принимает целое число и возвращает указанное количество простых чисел в виде Python-списка.

def primes(int kmax):
    cdef int n, k, i
    cdef int p[1000]
    result = []
    if kmax > 1000:
        kmax = 1000
    k = 0
    n = 2
    while k < kmax:
        i = 0
        while i < k and n % p[i] != 0:
            i = i + 1
        if i == k:
            p[k] = n
            k = k + 1
            result.append(n)
        n = n + 1
    return result

Как мы видим, начало ничем не отличается от обычного определения функции на Питоне, за исключением того, что тип параметра kmax объявляется как int. Это означает, что переданный параметр будет преобразован в C-тип integer (в случае невозможности такого преобразования будет вызвано исключение TypeError).

Строки 2 и 3 определяют несколько локальных переменных при помощи инструкции cdef. Строка 4 создает Python-список, в котором будет возвращен результат. Здесь стоит заметить, что тип переменной не указан (как и в чистом Питоне), поэтому она будет содержать Python-объект.

Строки 7-9 организуют цикл для проверки чисел на простоту, до тех пор, пока их не будет найдено указанное количество. Особенного внимания заслуживают строки 11-12, где происходит деление проверяемого числа на все найденные до сих пор простые числа. Поскольку в этом цикле не используются никакие Python-объекты, он целиком транслируется в код на чистом C и поэтому выполняется очень быстро.

Если число оказалось простым, в строках 14-15 оно добавляется в массив p для быстрого доступа внутри проверочного цикла, а строка 16 добавляет его в результирующий список. Строка 16 выглядит как обычное выражение на Питоне, и так оно, собственно, и есть, только параметр n автоматически конвертируется в Python-объект для передачи в метод append. И наконец, в строке 18 обычное Python-выражение возвращает результат.

После компиляции primes.pyx Cython’ом мы получаем модуль расширения, который можем тут же проверить в сессии интерпретатора:

>>> import primes
>>> primes.primes(10)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

Смотрите-ка, работает! Ну а если вам любопытно, как много труда вам сэкономил Cython, можете посмотреть на C-код, сгенерированный для этого модуля.

Применение языка

Cython особенно популярен среди научных пользователей Python, которые для него «идеальная аудитория», согласно словам разработчика Python Гвидо ван Россума. Отдельно стоит отметить:

  • Значительная часть научных библиотек вычислений SciPy, pandas и scikit-learn написаны в Cython.
  • Некоторые сайты с высоким трафиком, например Quora используют Cython.
  • Cython не ограничивается только численными вычислениями. Например, LXML XML-инструментарий написан в основном в Cython.
  • Как и его предшественник Pyrex, Cython используется для обеспечения привязки к Питону некоторых C и C++ библиотек, например библиотеки обмена сообщениями ZeroMQ.
  • Cython также может быть использован для разработки параллельных программ для машин многоядерных процессоров (с помощью библиотеки OpenMP).

Полезные ссылки