Прототипирование с использованием кода Python перед компиляцией

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

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

Что я должен знать о разработке функций / классов, которые, как я предполагаю, должны будут взаимодействовать с компилируемым языком? И сколько из этих потенциальных проблем решают такие библиотеки, как cTypes, bgen, SWIG, Boost.Python, Cython или Python SIP?

Для этого конкретного случая использования (подходящая библиотека) я представляю, что пользователи могут определять математические функции (Guassian, Lorentzian и т. Д.) Как функции Python, которые затем могут быть переданы интерпретированным библиотекой подбора скомпилированного кода. Передача и возврат массивов также имеет важное значение.

7 ответов

Решение

Наконец, вопрос, на который я действительно могу дать положительный ответ:).

Я исследовал f2py, boost.python, swig, cython и pyrex для моей работы (доктор философии по методам оптических измерений). Я широко использовал swig, boost.python, pyrex и cython. Я также использовал ctypes. Это моя разбивка:

Отказ от ответственности: это мой личный опыт. Я не связан ни с одним из этих проектов.

Swig: плохо работает с C++. Должно быть, но проблемы с именами на этапе компоновки были для меня большой головной болью в Linux и Mac OS X. Если у вас есть код на C и вы хотите, чтобы он был связан с python, это хорошее решение. Я упаковал GTS для своих нужд и мне нужно было написать в основном общую библиотеку C, к которой я мог бы подключиться. Я бы не рекомендовал это.

Ctypes: я написал оболочку libdc1394 (библиотека IEEE Camera), используя ctypes, и это был очень простой опыт. Вы можете найти код на https://launchpad.net/pydc1394. Преобразование заголовков в код Python требует больших усилий, но тогда все работает надежно. Это хороший способ, если вы хотите подключить внешнюю библиотеку. Ctypes также находится в stdlib Python, поэтому каждый может использовать ваш код прямо сейчас. Это также хороший способ быстро поиграть с новой библиотекой Python. Я могу рекомендовать его для взаимодействия с внешними библиотеками.

Boost.Python: очень приятно. Если у вас уже есть собственный код C++, который вы хотите использовать в python, сделайте это. Таким образом, очень легко перевести структуры классов С ++ в структуры классов Python. Я рекомендую это, если у вас есть код C++, который вам нужен в Python.

Pyrex / Cython: используйте Cython, а не Pyrex. Период. Cython более продвинутый и более приятный в использовании. В настоящее время я делаю все с Cython, что я делал с SWIG или Ctypes. Это также лучший способ, если у вас есть код Python, который работает слишком медленно. Процесс абсолютно фантастический: вы конвертируете свои модули Python в модули Cython, собираете их и продолжаете профилирование и оптимизацию, как будто это все еще был Python (никаких изменений инструментов не требуется). Затем вы можете применить столько же (или так мало) кода C, смешанного с вашим кодом Python. Это гораздо быстрее, чем переписывать целые части вашего приложения на C; Вы переписываете только внутренний цикл.

Время: ctypes имеет самые высокие издержки вызова (~700 нс), затем следует boost.python (322 нс), а затем непосредственно swig (290 нс). У Cython самая низкая нагрузка на вызов (124 нс) и лучшая обратная связь, на которую он тратит время (поддержка cProfile!). Числа из моего ящика вызывают тривиальную функцию, которая возвращает целое число из интерактивной оболочки; Поэтому накладные расходы на импорт модуля не рассчитаны, а только на вызовы функций. Поэтому проще и эффективнее быстро получить код на Python, используя профилирование и использование Cython.

Резюме: для вашей проблемы используйте Cython;). Я надеюсь, что это краткое изложение будет полезно для некоторых людей. Я с удовольствием отвечу на любой оставшийся вопрос.


Редактировать: я забыл упомянуть: для числовых целей (то есть подключение к NumPy) использовать Cython; у них есть поддержка для этого (потому что они в основном разрабатывают Cython для этой цели). Так что это должно быть еще один +1 для вашего решения.

Я не использовал SWIG или SIP, но считаю, что написание оболочек Python с boost.python очень мощно и относительно просто в использовании.

Я не совсем понимаю, каковы ваши требования для передачи типов между C/C++ и python, но вы можете легко сделать это, либо выставив тип C++ на python, либо используя общий аргумент boost::python::object для вашего C++ API. Вы также можете зарегистрировать конвертеры для автоматического преобразования типов Python в типы C++ и наоборот.

Если вы планируете использовать boost.python, этот учебник - хорошее место для начала.

Я реализовал нечто похожее на то, что вам нужно. У меня есть функция C++, которая принимает функцию python и изображение в качестве аргументов и применяет функцию python к каждому пикселю изображения.

Image* unary(boost::python::object op, Image& im)
{
    Image* out = new Image(im.width(), im.height(), im.channels());
    for(unsigned int i=0; i<im.size(); i++)
    {
        (*out)[i] == extract<float>(op(im[i]));
    }
    return out;
}

В этом случае Image - это объект C++, доступный для python (изображение с плавающими пикселями), а op - это функция, определенная для python (или фактически любой объект python с атрибутом __call__). Затем вы можете использовать эту функцию следующим образом (предполагая, что унарный находится в вызываемом образе, который также содержит Image и функцию загрузки):

import image
im = image.load('somefile.tiff')
double_im = image.unary(lambda x: 2.0*x, im)

Что касается использования массивов с boost, лично я этого не делал, но я знаю, что функциональность для представления массивов в python с использованием boost доступна - это может быть полезно.

Лучший способ спланировать возможный переход к скомпилированному коду - написать части, чувствительные к производительности, в виде модуля простых функций в функциональном стиле (без состояния и без побочных эффектов), которые принимают и возвращают базовые типы данных.

Это обеспечит взаимно-однозначное сопоставление вашего кода-прототипа Python с возможным скомпилированным кодом, и позволит вам легко использовать ctypes и избежать целого ряда головной боли.

Для пиковой подгонки вам почти наверняка понадобятся массивы, которые немного усложнят ситуацию, но все еще очень выполнимы для ctypes.

Если вы действительно хотите использовать более сложные структуры данных или модифицировать передаваемые аргументы, SWIG или стандартный интерфейс C-расширения Python позволит вам делать то, что вы хотите, но с некоторыми трудностями.

Для того, что вы делаете, вы также можете попробовать NumPy, который может выполнить часть работы, которую вы хотели бы перенести на C, а также предложить некоторую дополнительную помощь в перемещении данных между Python и C.

f2py (часть numpy) - это более простая альтернатива SWIG и boost.python для переноса кода C/Fortran.

По моему опыту, есть два простых способа вызова кода C из кода Python. Существуют и другие подходы, все они более раздражающие и / или многословные.

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

Второй самый простой способ - написать модуль Python на C, а затем вызвать функции в этом модуле. Вы можете передать все, что вы хотите, в эти функции C без необходимости проходить через любые обручи. И из этих функций на C легко вызывать функции или методы Python, как описано здесь: https://docs.python.org/extending/extending.html

У меня недостаточно опыта работы с SWIG, чтобы предлагать интеллектуальные комментарии. И хотя можно делать такие вещи, как передавать пользовательские объекты Python в функции C через ctypes, или определять новые классы Python в C, это раздражает и многословно, и я рекомендую воспользоваться одним из двух подходов, описанных выше.

В дополнение к вышеприведенным инструментам я могу порекомендовать использовать Pyrex (для создания модулей расширения Python) или Psyco (в качестве JIT-компилятора для Python).

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

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

Я не знаю, насколько это могло бы помочь, когда вы пытаетесь интегрировать код на C и Python, но я просто хотел исправить одно заблуждение.

Другие вопросы по тегам