Использование Python в качестве связующего звена
Предупреждение
Этот документ был написан в 2008 году как часть оригинальной книги Руководства по NumPy Трэвиса Э. Олифанта и устарел.
Многие считают Python замечательным языком для связывания различных компонентов. Надеемся, эта глава убедит вас в этом. Первые пользователи Python в научных вычислениях обычно использовали его для объединения крупных прикладных кодов, выполняемых на суперкомпьютерах. Python оказался значительно удобнее для программирования, чем shell-скрипты или Perl, а также возможность легко расширять Python позволила относительно просто создавать новые классы и типы, адаптированные к решаемым проблемам. В результате взаимодействия этих ранних разработчиков появился Numeric — объект, подобный массиву, который мог использоваться для передачи данных между приложениями.
По мере созревания и развития Numeric в NumPy пользователи смогли писать больше кода непосредственно в NumPy. Часто этот код достаточно быстрый для использования в производстве, но иногда всё же возникает необходимость доступа к скомпилированному коду. Возможно, для достижения максимальной производительности алгоритма или для более простого доступа к широко распространенному коду, написанному на C/C++ или Fortran.
В этой главе будут рассмотрены многие инструменты, доступные для доступа к коду, написанному на других скомпилированных языках. Существует много ресурсов для изучения вызова других скомпилированных библиотек из Python, и цель этой главы — не сделать вас экспертом. Главная цель — ознакомить вас с некоторыми возможностями, чтобы вы знали, что искать в Google, чтобы узнать больше.
Вызов других скомпилированных библиотек из Python
Хотя Python — замечательный язык и удовольствие программировать на нём, его динамическая природа приводит к накладным расходам, которые могут привести к тому, что некоторые фрагменты кода (например, вычисления внутри циклов for) будут выполняться в 10-100 раз медленнее, чем эквивалентный код, написанный на статически скомпилированном языке. Кроме того, это может привести к большему потреблению памяти, так как временные массивы создаются и уничтожаются во время вычислений. Для многих типов вычислений дополнительные замедление и потребление памяти часто не могут быть скомпенсированы (по крайней мере, для критически важных для времени или памяти частей вашего кода). Поэтому одна из наиболее распространённых потребностей — вызов быстрой машинной инструкции (например, скомпилированной с помощью C/C++ или Fortran) из кода Python. Простота такого вызова — одна из причин, по которой Python является отличным языком высокого уровня для научного и инженерного программирования.
Существует два основных подхода к вызову скомпилированного кода: написание модуля расширения, который затем импортируется в Python с помощью команды import, или прямой вызов подпрограммы разделяемой библиотеки из Python с помощью модуля ctypes. Написание модуля расширения — наиболее распространённый метод.
Предупреждение
Вызов кода C из Python может привести к сбою Python, если вы не внимательны. Ни один из подходов в этой главе не застрахован от этого. Вам необходимо знать о том, как обрабатываются данные в NumPy и в используемой сторонней библиотеке.
Самостоятельно созданные обёртки
Модули расширения были рассмотрены в Написание модуля расширения. Наиболее простой способ взаимодействия со скомпилированным кодом — создание модуля расширения и построение метода модуля, который вызывает скомпилированный код. Для улучшения читаемости ваш метод должен использовать PyArg_ParseTuple для преобразования между объектами Python и типами данных C. Для стандартных типов данных C, вероятно, уже существует встроенный преобразователь. Для других вам может потребоваться написать свой преобразователь и использовать строку формата "O&", которая позволяет указать функцию, которая будет использоваться для преобразования объекта Python в необходимые структуры C.
После преобразования в соответствующие структуры C и типы данных C следующим шагом в обёртке является вызов подлежащей функции. Это просто, если подлежащая функция написана на C или C++. Однако для вызова кода Fortran вам необходимо знать, как вызывать подпрограммы Fortran из C/C++ с помощью вашего компилятора и платформы. Это может незначительно различаться на платформах и компиляторах (ещё одна причина, по которой f2py упрощает взаимодействие с кодом Fortran), но в общем случае это включает в себя подчеркивание имени и то, что все переменные передаются по ссылке (то есть все аргументы являются указателями).
Преимущества самостоятельно созданной обёртки заключаются в том, что у вас есть полный контроль над тем, как используется и вызывается библиотека C, что может привести к надёжному и плотному интерфейсу с минимальными накладными расходами. Недостатком является то, что вам нужно писать, отлаживать и поддерживать код C, хотя большая часть его может быть адаптирована с помощью проверенного временем метода «вырезать-вставить-изменить» из других модулей расширения. Поскольку процедура вызова дополнительного кода C довольно регламентирована, были разработаны процедуры генерации кода, чтобы упростить этот процесс. Один из этих методов генерации кода поставляется с NumPy и позволяет легко интегрировать код Fortran и (простой) код C. Этот пакет, f2py, будет кратко рассмотрен в следующей секции.
F2PY
F2PY позволяет автоматически создавать модуль расширения, который взаимодействует с процедурами кода Fortran 77/90/95. Он умеет анализировать код Fortran 77/90/95 и автоматически генерировать подписи Python для встречающихся подпрограмм, или вы можете направить взаимодействие подпрограмм с Python, создав файл определения интерфейса (или изменив созданный f2py).
См. документацию по F2PY для получения дополнительной информации и примеров.
Метод f2py связывания скомпилированного кода в настоящее время является наиболее сложным и интегрированным подходом. Он позволяет чётко разделить Python и скомпилированный код, при этом всё ещё позволяя отдельно распространять модуль расширения. Единственный недостаток заключается в том, что для установки кода пользователю необходим Fortran-компилятор. Однако, с наличием бесплатных компиляторов g77, gfortran и g95, а также качественных коммерческих компиляторов, это ограничение не является существенным. По нашему мнению, Fortran по-прежнему является самым простым способом написания быстрого и понятного кода для научных вычислений. Он наиболее простым образом обрабатывает комплексные числа и многомерную индексацию. Однако имейте в виду, что некоторые Fortran-компиляторы не смогут оптимизировать код так же хорошо, как хорошо написанный код C.
Cython
Cython — это компилятор для диалекта Python, который добавляет (по желанию) статическую типизацию для повышения скорости и позволяет смешивать код C или C++ в ваши модули. Он генерирует расширения на C или C++, которые можно скомпилировать и импортировать в коде Python.
Если вы пишете модуль расширения, который будет включать значительное количество собственного алгоритмического кода, то Cython является хорошим выбором. Среди его возможностей — возможность лёгкой и быстрой работы с многомерными массивами.
Обратите внимание, что Cython — это только генератор модулей расширения. В отличие от f2py, он не включает автоматического средства компиляции и компоновки модуля расширения (что необходимо выполнить обычным способом). Он предоставляет изменённый класс distutils под названием build_ext, который позволяет вам создать модуль расширения из .pyx исходного кода. Таким образом, вы можете написать в файле setup.py:
from Cython.Distutils import build_ext
from distutils.extension import Extension
from distutils.core import setup
import numpy
setup(name='mine', description='Nothing',
ext_modules=[Extension('filter', ['filter.pyx'],
include_dirs=[numpy.get_include()])],
cmdclass = {'build_ext':build_ext})
Добавление каталога включения NumPy, конечно, необходимо только в том случае, если вы используете массивы NumPy в модуле расширения (для чего, собственно, мы и используем Cython). Расширения distutils в NumPy также включают поддержку автоматического создания модуля расширения и его компоновки из файла .pyx. При этом, если у пользователя не установлен Cython, он ищет файл с тем же именем, но с расширением .c, которое затем используется вместо попытки повторного создания файла .c.
Если вы просто используете Cython для компиляции стандартного модуля Python, то получите модуль расширения на C, который, как правило, работает немного быстрее, чем эквивалентный модуль Python. Дальнейшее ускорение можно получить, используя ключевое слово cdef, чтобы статически определить переменные C.
Давайте рассмотрим два примера, которые мы видели ранее, чтобы увидеть, как они могут быть реализованы с использованием Cython. Эти примеры были скомпилированы в модули расширения с помощью Cython 0.21.1.
Сложение комплексных чисел в Cython
Вот часть модуля Cython под названием add.pyx, который реализует функции сложения комплексных чисел, которые мы ранее реализовывали с помощью f2py:
cimport cython
cimport numpy as np
import numpy as np
# We need to initialize NumPy.
np.import_array()
#@cython.boundscheck(False)
def zadd(in1, in2):
cdef double complex[:] a = in1.ravel()
cdef double complex[:] b = in2.ravel()
out = np.empty(a.shape[0], np.complex64)
cdef double complex[:] c = out.ravel()
for i in range(c.shape[0]):
c[i].real = a[i].real + b[i].real
c[i].imag = a[i].imag + b[i].imag
return out
Этот модуль демонстрирует использование оператора cimport, чтобы загрузить определения из numpy.pxd заголовка, поставляемого вместе с Cython. Похоже, что NumPy импортируется дважды; cimport лишь делает доступной NumPy C-API, тогда как обычный импорт import вызывает импорт в стиле Python во время выполнения и позволяет обращаться к знакомой NumPy Python API.
Пример также демонстрирует «видовые представления памяти» Cython, которые подобны массивам NumPy на уровне C, в том смысле, что они представляют собой сформированные и индексированные массивы, которые знают о своих размерах (в отличие от массива C, адресуемого через обычный указатель). Синтаксис double complex[:] обозначает одномерный массив (вектор) двойных чисел с произвольными шагами. Соседний массив целых чисел был бы int[::1], а матрица чисел с плавающей точкой — float[:, :].
Закомментированным показан декоратор cython.boundscheck, который включает или отключает проверку границ для доступа к видовым представлениям памяти на основе функций. Мы можем использовать это, чтобы ещё больше ускорить код, ценой безопасности (или ручной проверки перед входом в цикл).
Помимо синтаксиса представления, функция сразу же понятна программисту Python. Статическая типизация переменной i неявная. Вместо синтаксиса представления, мы также могли бы использовать специальный синтаксис массивов NumPy Cython, но синтаксис представления предпочтительнее.
Фильтр изображений в Cython
Двумерный пример, который мы создали с помощью Fortran, так же легко написать на Cython:
cimport numpy as np
import numpy as np
np.import_array()
def filter(img):
cdef double[:, :] a = np.asarray(img, dtype=np.double)
out = np.zeros(img.shape, dtype=np.double)
cdef double[:, ::1] b = out
cdef np.npy_intp i, j
for i in range(1, a.shape[0] - 1):
for j in range(1, a.shape[1] - 1):
b[i, j] = (a[i, j]
+ .5 * ( a[i-1, j] + a[i+1, j]
+ a[i, j-1] + a[i, j+1])
+ .25 * ( a[i-1, j-1] + a[i-1, j+1]
+ a[i+1, j-1] + a[i+1, j+1]))
return out
Этот двумерный фильтр усреднения работает быстро, потому что цикл находится на C, а вычисления указателей выполняются только по мере необходимости. Если приведенный выше код скомпилирован как модуль image, то 2-d изображение img может быть отфильтровано с помощью этого кода очень быстро с помощью:
import image out = image.filter(img)
Что касается кода, два момента заслуживают внимания: во-первых, невозможно вернуть видовое представление в Python. Вместо этого сначала создается массив NumPy out, а затем используется представление b этого массива для вычислений. Во-вторых, видовое представление b имеет тип double[:, ::1]. Это означает 2-мерный массив с соседними строками, т.е., порядок матрицы C. Явное указание порядка может ускорить некоторые алгоритмы, поскольку они могут пропустить вычисления шагов.
Заключение
Cython — это механизм расширения, используемый несколькими научными библиотеками Python, включая Scipy, Pandas, SAGE, scikit-image и scikit-learn, а также библиотекой обработки XML LXML. Язык и компилятор хорошо поддерживаются.
Существуют несколько недостатков использования Cython:
- При написании пользовательских алгоритмов и иногда при обёртке существующих библиотек C требуется некоторое знакомство с C. В частности, при использовании управления памятью C (
mallocи аналогичные функции) легко допустить утечку памяти. Однако, просто компиляция модуля Python с переименованием в.pyxуже может ускорить его, а добавление нескольких объявлений типов может дать значительное ускорение в некоторых кодах. - Легко потерять чёткое разделение между Python и C, что затрудняет повторное использование вашего C-кода для других проектов, не связанных с Python.
- C-код, сгенерированный Cython, трудно читать и изменять (и обычно компилируется с надоедливыми, но безобидными предупреждениями).
Одним из больших преимуществ модулей расширения, сгенерированных с помощью Cython, является простота их распространения. В заключение, Cython — очень мощный инструмент для быстрого связывания кода C или генерации модуля расширения и не должен игнорироваться. Он особенно полезен для людей, которые не могут или не хотят писать код на C или Fortran.
ctypes
ctypes — это модуль расширения Python, включенный в stdlib, который позволяет вызывать произвольную функцию в общей библиотеке напрямую из Python. Этот подход позволяет взаимодействовать с кодом C непосредственно из Python. Это открывает огромное количество библиотек для использования из Python. Однако недостатком является то, что ошибки программирования могут очень легко привести к неприглядным аварийным завершениям программы (как и в C), потому что проверка типов и границ параметров выполняется слабо. Это особенно верно, когда данные массива передаются как указатель на сырое местоположение памяти. Тогда ответственность лежит на вас, чтобы подпрограмма не обращалась к памяти за пределами фактической области массива. Но если вы не против немного рисковать, ctypes может быть эффективным инструментом для быстрого использования большой общей библиотеки (или написания расширенной функциональности в вашей собственной общей библиотеке).
Поскольку подход ctypes предоставляет прямой доступ к скомпилированному коду, он не всегда устойчив к ошибкам пользователя. Надежное использование модуля ctypes обычно включает дополнительный уровень кода Python для проверки типов данных и границ массивов объектов, передаваемых в подпрограмму нижнего уровня. Этот дополнительный уровень проверки (не говоря уже о преобразовании объектов ctypes в типы данных C, которое выполняет сам ctypes), сделает интерфейс медленнее, чем интерфейс расширения, написанный вручную. Однако это дополнительное время должно быть незначительным, если вызываемая процедура C выполняет значительный объем работы. Если вы отличный программист Python с слабыми навыками C, ctypes — это простой способ создать полезный интерфейс для (общей) библиотеки скомпилированного кода.
Для использования ctypes необходимо:
- Наличие общей библиотеки.
- Загрузка общей библиотеки.
- Преобразование объектов Python в аргументы, понятные ctypes.
- Вызов функции из библиотеки с аргументами ctypes.
Преобразование аргументов
Целые числа/длинные целые числа Python, строки и объекты unicode автоматически преобразуются в эквивалентные аргументы ctypes по мере необходимости. Объект None также автоматически преобразуется в указатель NULL. Все остальные объекты Python должны быть преобразованы в специфичные для ctypes типы. Существует два способа обойти это ограничение, позволяющие ctypes интегрироваться с другими объектами.
- Не устанавливайте атрибут argtypes объекта функции и определите метод
_as_parameter_для объекта, который вы хотите передать. Метод_as_parameter_должен возвращать целое число Python, которое будет передано напрямую в функцию. - Установите атрибут argtypes в список, элементы которого содержат объекты с методом класса from_param, который знает, как преобразовать ваш объект в объект, который ctypes может понять (целое число/длинное целое число, строка, unicode или объект с атрибутом
_as_parameter_).
NumPy использует оба метода, отдавая предпочтение второму методу, потому что он может быть безопаснее. Атрибут ctypes ndarray возвращает объект, который имеет атрибут _as_parameter_, который возвращает целое число, представляющее адрес ndarray, к которому он связан. В результате можно напрямую передать этот объект атрибута ctypes функции, ожидающей указатель на данные в вашем ndarray. Вызывающий метод должен убедиться, что объект ndarray имеет правильный тип, форму и правильно установленные флаги, чтобы избежать неприятных сбоев, если указатель данных на неподходящие массивы будут переданы.
Для реализации второго метода NumPy предоставляет функцию создания класса ndpointer в модуле numpy.ctypeslib. Эта функция создания класса создает подходящий класс, который может быть помещен в запись атрибута argtypes функции ctypes. Класс будет содержать метод from_param, который ctypes будет использовать для преобразования любого ndarray, переданного в функцию, в объект, распознаваемый ctypes. В процессе преобразования будет выполнена проверка любых свойств ndarray, указанных пользователем в вызове ndpointer. Проверяемые аспекты ndarray включают тип данных, количество измерений, форму и/или состояние флагов любого переданного массива. Возвращаемое значение метода from_param — это атрибут ctypes массива, который (поскольку он содержит атрибут _as_parameter_, указывающий на область данных массива) может быть напрямую использован ctypes.
Атрибут ctypes ndarray также обладает дополнительными атрибутами, которые могут быть удобны при передаче дополнительной информации об массиве в функцию ctypes. Атрибуты data, shape и strides могут предоставить типы, совместимые с ctypes, соответствующие области данных, форме и шагам массива. Атрибут data возвращает c_void_p, представляющий указатель на область данных. Атрибуты shape и strides каждый возвращают массив целых чисел ctypes (или None, представляющий указатель NULL, если массив 0-мерный). Базовый тип ctype массива — это целое число ctype того же размера, что и указатель на платформе. Также есть методы data_as({ctype}), shape_as(<base ctype>), и strides_as(<base
ctype>). Они возвращают данные как объект ctype вашего выбора и массивы shape/strides, используя базовый тип вашего выбора. Для удобства модуль ctypeslib также содержит c_intp как тип данных ctypes целого числа, размер которого соответствует размеру c_void_p на платформе (его значение равно None, если ctypes не установлен).
Вызов функции
Функция доступна как атрибут или элемент загруженной библиотеки общего использования. Таким образом, если ./mylib.so содержит функцию с именем cool_function1, она может быть обращена следующим образом:
lib = numpy.ctypeslib.load_library('mylib','.')
func1 = lib.cool_function1 # or equivalently
func1 = lib['cool_function1']
В ctypes значение возврата функции по умолчанию задано как «int». Это поведение можно изменить, установив атрибут restype функции. Используйте None для restype, если функция не имеет значения возврата («void»):
func1.restype = None
Как уже обсуждалось, вы также можете установить атрибут argtypes функции, чтобы ctypes проверял типы входных аргументов при вызове функции. Используйте фабричную функцию ndpointer для создания готового класса для проверки типа данных, формы и флагов для вашей новой функции. Функция ndpointer имеет следующий синтаксис
- ndpointer(dtype=None, ndim=None, shape=None, flags=None)
-
Ключевые аргументы со значением
Noneне проверяются. Указание ключевого слова принуждает проверку данного аспекта ndarray при преобразовании в совместимый с ctypes объект. Ключевое слово dtype может быть любым объектом, понимаемым как объект типа данных. Ключевое слово ndim должно быть целым числом, а ключевое слово shape должно быть целым числом или последовательностью целых чисел. Ключевое слово flags указывает минимальные флаги, которые требуются для любого массива, передаваемого на вход. Это может быть указано как строка с запятыми, отделяющими требования, целое число, представляющее требования, объединённые с помощью оператора «ИЛИ», или объект flags, возвращённый из атрибута flags массива с необходимыми требованиями.
Использование класса ndpointer в методе argtypes может значительно повысить безопасность вызова C-функции с помощью ctypes и области данных ndarray. Вы по-прежнему можете обернуть функцию дополнительной оболочкой Python для удобства использования (скрывая некоторые очевидные аргументы и делая некоторые аргументы аргументами вывода). В этом процессе функция requires в NumPy может быть полезна для возврата нужного типа массива из заданного входного массива.
Полный пример
В этом примере мы продемонстрируем, как функцию сложения и функцию фильтра, реализованные ранее с помощью других подходов, можно реализовать с помощью ctypes. Сначала код на C, реализующий алгоритмы, содержит функции zadd, dadd, sadd, cadd, и dfilter2d. Функция zadd выглядит так:
/* Add arrays of contiguous data */
typedef struct {double real; double imag;} cdouble;
typedef struct {float real; float imag;} cfloat;
void zadd(cdouble *a, cdouble *b, cdouble *c, long n)
{
while (n--) {
c->real = a->real + b->real;
c->imag = a->imag + b->imag;
a++; b++; c++;
}
}
аналогичный код для cadd, dadd, и sadd обрабатывает сложные типы данных float, double и float, соответственно:
void cadd(cfloat *a, cfloat *b, cfloat *c, long n)
{
while (n--) {
c->real = a->real + b->real;
c->imag = a->imag + b->imag;
a++; b++; c++;
}
}
void dadd(double *a, double *b, double *c, long n)
{
while (n--) {
*c++ = *a++ + *b++;
}
}
void sadd(float *a, float *b, float *c, long n)
{
while (n--) {
*c++ = *a++ + *b++;
}
}
Файл code.c также содержит функцию dfilter2d:
/*
* Assumes b is contiguous and has strides that are multiples of
* sizeof(double)
*/
void
dfilter2d(double *a, double *b, ssize_t *astrides, ssize_t *dims)
{
ssize_t i, j, M, N, S0, S1;
ssize_t r, c, rm1, rp1, cp1, cm1;
M = dims[0]; N = dims[1];
S0 = astrides[0]/sizeof(double);
S1 = astrides[1]/sizeof(double);
for (i = 1; i < M - 1; i++) {
r = i*S0;
rp1 = r + S0;
rm1 = r - S0;
for (j = 1; j < N - 1; j++) {
c = j*S1;
cp1 = j + S1;
cm1 = j - S1;
b[i*N + j] = a[r + c] +
(a[rp1 + c] + a[rm1 + c] +
a[r + cp1] + a[r + cm1])*0.5 +
(a[rp1 + cp1] + a[rp1 + cm1] +
a[rm1 + cp1] + a[rm1 + cp1])*0.25;
}
}
}
Одно из возможных преимуществ этого кода перед эквивалентным кодом на Fortran заключается в том, что он обрабатывает произвольно сгруппированные (то есть несмежные массивы) и, возможно, работает быстрее в зависимости от возможностей оптимизации вашего компилятора. Однако он явно сложнее, чем простой код в filter.f. Этот код необходимо скомпилировать в библиотеку общего использования. На моей системе Linux это достигается с помощью:
gcc -o code.so -shared code.c
Что создаёт библиотеку общего использования code.so в текущем каталоге. В Windows не забудьте либо добавить __declspec(dllexport) перед void в строке перед определением каждой функции, либо создать файл code.def, который перечисляет имена экспортируемых функций.
Должна быть создана соответствующая Python-обёртка для этой библиотеки общего использования. Для этого создайте файл interface.py с указанными строками в начале:
__all__ = ['add', 'filter2d']
import numpy as np
import os
_path = os.path.dirname('__file__')
lib = np.ctypeslib.load_library('code', _path)
_typedict = {'zadd' : complex, 'sadd' : np.single,
'cadd' : np.csingle, 'dadd' : float}
for name in _typedict.keys():
val = getattr(lib, name)
val.restype = None
_type = _typedict[name]
val.argtypes = [np.ctypeslib.ndpointer(_type,
flags='aligned, contiguous'),
np.ctypeslib.ndpointer(_type,
flags='aligned, contiguous'),
np.ctypeslib.ndpointer(_type,
flags='aligned, contiguous,'\
'writeable'),
np.ctypeslib.c_intp]
Этот код загружает библиотеку общего использования с именем code.{ext}, расположенную в том же каталоге, что и этот файл. Затем он добавляет тип возвращаемого значения void к функциям, содержащимся в библиотеке. Он также добавляет проверку аргументов для функций в библиотеке, чтобы ndarrays могли передаваться в качестве первых трёх аргументов вместе с целым числом (достаточно большим для хранения указателя на платформе) в качестве четвёртого аргумента.
Настройка функции фильтра аналогична и позволяет вызывать функцию фильтра с аргументами ndarray в качестве первых двух аргументов и указателями на целые числа (достаточно большими для обработки сдвигов и формы ndarray) в качестве последних двух аргументов.:
lib.dfilter2d.restype=None
lib.dfilter2d.argtypes = [np.ctypeslib.ndpointer(float, ndim=2,
flags='aligned'),
np.ctypeslib.ndpointer(float, ndim=2,
flags='aligned, contiguous,'\
'writeable'),
ctypes.POINTER(np.ctypeslib.c_intp),
ctypes.POINTER(np.ctypeslib.c_intp)]
Далее определим простую функцию выбора, которая выбирает, какую функцию сложения вызывать в библиотеке общего использования на основе типа данных:
def select(dtype):
if dtype.char in ['?bBhHf']:
return lib.sadd, single
elif dtype.char in ['F']:
return lib.cadd, csingle
elif dtype.char in ['DG']:
return lib.zadd, complex
else:
return lib.dadd, float
return func, ntype
Наконец, две экспортируемые функции интерфейса можно записать просто как:
def add(a, b):
requires = ['CONTIGUOUS', 'ALIGNED']
a = np.asanyarray(a)
func, dtype = select(a.dtype)
a = np.require(a, dtype, requires)
b = np.require(b, dtype, requires)
c = np.empty_like(a)
func(a,b,c,a.size)
return c
и:
def filter2d(a):
a = np.require(a, float, ['ALIGNED'])
b = np.zeros_like(a)
lib.dfilter2d(a, b, a.ctypes.strides, a.ctypes.shape)
return b
Заключение
Использование ctypes — мощный способ связи Python с произвольным кодом C. Его преимущества для расширения Python включают
-
чёткое разделение кода C от кода Python
- нет необходимости изучать новый синтаксис, кроме Python и C
- позволяет повторно использовать код C
- функциональность из библиотек общего использования, написанных для других целей, может быть получена с помощью простой Python-обёртки и поиска библиотеки.
- лёгкая интеграция с NumPy через атрибут ctypes
- полная проверка аргументов с фабрикой класса ndpointer
Недостатки включают
- трудно распространять модуль расширения, созданный с использованием ctypes, из-за отсутствия поддержки построения библиотек общего использования в distutils.
- необходимо иметь библиотеки общего использования вашего кода (нет статических библиотек).
- очень мало поддержки кода C++ и его различных соглашений о вызове библиотек. Вероятно, вам понадобится C-обёртка вокруг кода C++, чтобы использовать его с ctypes (или просто используйте Boost.Python).
Из-за трудностей распространения модуля расширения, созданного с использованием ctypes, f2py и Cython по-прежнему являются наиболее простыми способами расширения Python для создания пакетов. Однако ctypes в некоторых случаях является полезной альтернативой. Это должно добавить больше функций в ctypes, что должно устранить трудности при расширении Python и распространении расширения с помощью ctypes.
Дополнительные инструменты, которые могут пригодиться
Эти инструменты были полезны другим пользователям Python, и поэтому они включены здесь. Они обсуждаются отдельно, потому что это либо устаревшие способы выполнения задач, которые сейчас обрабатываются f2py, Cython или ctypes (SWIG, PyFort), либо из-за отсутствия разумной документации (SIP, Boost). Ссылки на эти методы не включены, так как наиболее релевантные можно найти с помощью Google или другого поискового движка, и любые предоставленные здесь ссылки быстро устареют. Не предполагайте, что включение в этот список означает, что пакет заслуживает внимания. Информация об этих пакетах собрана здесь, потому что многие люди нашли их полезными, и мы хотели предоставить вам как можно больше вариантов решения проблемы лёгкой интеграции вашего кода.
SWIG
Simplified Wrapper and Interface Generator (SWIG) — старый и довольно стабильный метод обертывания C/C++ библиотек в большое количество других языков. Он не понимает массивы NumPy напрямую, но может быть сделан пригодным для использования с NumPy с помощью typemap. Некоторые примеры typemap находятся в каталоге numpy/tools/swig под numpy.i вместе с демонстрационным модулем, использующим их. SWIG превосходит при обертывании больших C/C++ библиотек, потому что он может (почти) распарсить их заголовки и автоматически сгенерировать интерфейс. Технически, вам нужно сгенерировать файл .i , который определяет интерфейс. Однако часто этот файл .i может быть частью самого заголовка. Интерфейс обычно требует некоторой корректировки, чтобы быть очень полезным. Эта возможность парсить заголовки C/C++ и автоматически генерировать интерфейс всё ещё делает SWIG полезным подходом к добавлению функциональности из C/C++ в Python, несмотря на появившиеся более специализированные методы для Python. SWIG на самом деле может создавать расширения для нескольких языков, но typemap обычно должны быть специфичными для языка. Тем не менее, с изменениями в Python-специфичных typemap SWIG может использоваться для интерфейса библиотеки с другими языками, такими как Perl, Tcl и Ruby.
Мой опыт с SWIG был в целом положительным, так как он относительно прост в использовании и довольно мощный. Он часто использовался до того, как я стал более опытным в написании C-расширений. Однако создание пользовательских интерфейсов с помощью SWIG часто представляет трудности, потому что это необходимо делать с использованием концепции typemap, которые не специфичны для Python и написаны в синтаксисе, похожем на C. Поэтому предпочтительны другие стратегии склеивания, и SWIG, вероятно, будет рассмотрен только для обёртки очень большой C/C++ библиотеки. Тем не менее, есть и другие, кто использует SWIG вполне удовлетворённо.
SIP
SIP — ещё один инструмент для обертывания C/C++ библиотек, специфичный для Python, и, похоже, имеет очень хорошую поддержку C++. Компания Riverbank Computing разработала SIP для создания Python-связей с библиотекой QT. Для генерации связи необходимо написать файл интерфейса, но файл интерфейса выглядит очень похожим на файл заголовка C/C++. Хотя SIP не является полным парсером C++, он понимает довольно много синтаксиса C++, а также свои собственные специальные директивы, которые позволяют изменять способ создания Python-связей. Он также позволяет пользователю определять соответствия между типами Python и структурами и классами C/C++.
Boost Python
Boost — хранилище C++ библиотек, и Boost.Python — одна из таких библиотек, которая предоставляет лаконичный интерфейс для связывания C++ классов и функций с Python. Удивительная часть подхода Boost.Python заключается в том, что он работает полностью на чистом C++ без введения нового синтаксиса. Многие пользователи C++ сообщают, что Boost.Python позволяет бесшовно объединить лучшее из обоих миров. Использование Boost для обертывания простых C-подпрограмм обычно является перебором. Его основное назначение — сделать доступными C++ классы в Python. Итак, если у вас есть набор C++ классов, которые необходимо чисто интегрировать в Python, изучите и используйте Boost.Python.
Pyfort
Pyfort — хороший инструмент для обертывания кода Fortran и похожих на Fortran C-кодов в Python с поддержкой массивов Numeric. Он был написан Полом Дюбуа, выдающимся учёным-компьютерщиком и первым разработчиком Numeric (теперь на пенсии). Стоит упомянуть в надежде, что кто-то обновит PyFort для работы с массивами NumPy, которые теперь поддерживают либо массивы Fortran, либо массивы C.
© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/user/c-info.python-as-glue.html