Использование Python в качестве связующего звена#
Предупреждение
Это было написано в 2008 году как часть оригинального Руководство по NumPy книга Трэвиса Э. Олифанта и устарела.
Многие любят говорить, что Python — это фантастический язык-клей. Надеюсь, эта глава убедит вас, что это правда. Первые пользователи Python в науке обычно применяли его для связывания больших прикладных кодов, работающих на суперкомпьютерах. Не только программировать на Python было гораздо приятнее, чем на shell-скриптах или Perl, но и возможность легко расширять Python делала относительно простым создание новых классов и типов, специально адаптированных к решаемым задачам. Из взаимодействия этих ранних участников возник Numeric как объект, похожий на массив, который можно было использовать для передачи данных между этими приложениями.
По мере развития Numeric в NumPy, люди получили возможность писать больше кода непосредственно на NumPy. Часто этот код достаточно быстр для производственного использования, но всё ещё бывают случаи, когда требуется доступ к скомпилированному коду. Либо для достижения максимальной эффективности алгоритма, либо для упрощения доступа к широко доступным кодам, написанным на C/C++ или Fortran.
В этой главе будут рассмотрены многие инструменты, доступные для доступа к коду, написанному на других компилируемых языках. Существует множество ресурсов для изучения вызова других скомпилированных библиотек из Python, и цель этой главы не в том, чтобы сделать вас экспертом. Основная цель — ознакомить вас с некоторыми возможностями, чтобы вы знали, что «погуглить» для дальнейшего изучения.
Вызов других скомпилированных библиотек из Python#
Хотя Python — отличный язык и приятен для программирования, его динамическая природа приводит к накладным расходам, которые могут вызывать некоторые проблемы в коде ( т.е. сырые вычисления внутри циклов for) могут быть в 10-100 раз медленнее, чем эквивалентный код, написанный на статически компилируемом языке. Кроме того, это может привести к увеличению использования памяти, поскольку временные массивы создаются и уничтожаются во время вычислений. Для многих типов вычислительных задач дополнительное замедление и потребление памяти часто недопустимы (по крайней мере, для временно- или память- критических частей вашего кода). Поэтому одна из самых распространённых потребностей — вызов из кода Python в быструю подпрограмму машинного кода (например, скомпилированную с использованием C/C++ или Fortran). Тот факт, что это относительно легко сделать, является большой причиной, почему 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})
Добавление каталога include 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 только делает C-API NumPy доступным, в то время как обычный import вызывает импорт в стиле Python во время выполнения и позволяет вызывать знакомый Python API NumPy.
Пример также демонстрирует "типизированные представления памяти" 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, затем 2D изображение, img, можно быстро отфильтровать
с помощью этого кода:
import image
out = image.filter(img)
Что касается кода, следует отметить две вещи: во-первых, невозможно вернуть представление памяти в Python. Вместо этого массив NumPy out сначала создаётся, а затем представление b на этот массив используется для вычисления. Во-вторых, представление b имеет тип double[:, ::1]Это означает двумерный массив с непрерывными строками, т.е. порядок матрицы 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, включенным в стандартную библиотеку, который позволяет вызывать произвольную функцию в общей библиотеке напрямую из Python. Этот подход позволяет взаимодействовать с C-кодом напрямую из Python. Это открывает огромное количество библиотек для использования из Python. Недостаток, однако, в том, что ошибки кодирования могут легко привести к неприятным сбоям программы (как может случиться в C), потому что проверка типов или границ параметров почти не выполняется. Это особенно верно, когда данные массива передаются как указатель на необработанное место в памяти. Тогда ответственность лежит на вас, чтобы подпрограмма не обращалась к памяти за пределами фактической области массива. Но если вы не против немного рискнуть, ctypes может быть эффективным инструментом для быстрого использования большой общей библиотеки (или написания расширенной функциональности в вашей собственной общей библиотеке).
Поскольку подход ctypes предоставляет необработанный интерфейс к скомпилированному коду, он не всегда терпим к ошибкам пользователя. Надежное использование модуля ctypes обычно включает дополнительный слой кода Python для проверки типов данных и границ массивов объектов, передаваемых в базовую подпрограмму. Этот дополнительный слой проверки (не говоря уже о преобразовании объектов ctypes в типы данных C, которое выполняет сам ctypes) сделает интерфейс медленнее, чем интерфейс модуля расширения, написанный вручную. Однако эти накладные расходы должны быть незначительными, если вызываемая C-подпрограмма выполняет значительный объем работы. Если вы опытный программист на Python со слабыми навыками C, ctypes — это простой способ написать полезный интерфейс к (общей) библиотеке скомпилированного кода.
Чтобы использовать ctypes, вы должны
Иметь общую библиотеку.
Загрузить общую библиотеку.
Преобразуйте объекты Python в аргументы, понятные для ctypes.
Вызовите функцию из библиотеки с аргументами ctypes.
Преобразование аргументов#
Целые числа Python ints/longs, строки и объекты unicode автоматически преобразуются по мере необходимости в эквивалентные аргументы ctypes. Объект None также автоматически преобразуется в указатель NULL. Все остальные объекты Python должны быть преобразованы в специфичные для ctypes типы. Существует два способа обойти это ограничение, позволяющие ctypes интегрироваться с другими объектами.
Не устанавливайте атрибут argtypes объекта функции и определите
_as_parameter_метод для объекта, который вы хотите передать. The_as_parameter_метод должен возвращать Python int, который будет передан непосредственно в функцию.Установите атрибут argtypes в список, записи которого содержат объекты с методом класса from_param, который знает, как преобразовать ваш объект в объект, понятный ctypes (int/long, строка, 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. Атрибуты данные,
shape, и strides может предоставить типы, совместимые с ctypes,
соответствующие области данных, форме и шагам
массива. Атрибут data возвращает c_void_p представляющий указатель на область данных. Атрибуты shape и strides каждый возвращают массив целых чисел ctypes (или None, представляющий нулевой указатель, если массив 0-d). Базовый ctype массива — это целочисленный ctype того же размера, что и указатель на платформе. Также есть методы
data_as({ctype}), shape_as(, и strides_as(. Эти методы возвращают данные как объект ctype выбранного типа и
массивы формы/шагов, используя базовый тип по вашему выбору.
Для удобства, 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, флаги=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++;
}
}
The 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 через использование typemaps. Есть несколько
примеров typemaps в директории numpy/tools/swig под именем numpy.i вместе
с примером модуля, который их использует. SWIG превосходен в обёртке
больших библиотек C/C++, потому что он может (почти) разбирать их заголовки и
автоматически создавать интерфейс. Технически вам нужно сгенерировать .i
файл, определяющий интерфейс. Однако часто этот .i файл может быть частью самого заголовка. Интерфейс обычно требует небольшой настройки, чтобы быть очень полезным. Эта возможность анализировать заголовки C/C++ и автоматически генерировать интерфейс по-прежнему делает SWIG полезным подходом для добавления функциональности из C/C++ в Python, несмотря на другие методы, которые появились и более ориентированы на Python. SWIG фактически может создавать расширения для нескольких языков, но typemaps обычно должны быть специфичными для языка. Тем не менее, с модификациями Python-специфичных typemaps, SWIG можно использовать для интерфейса библиотеки с другими языками, такими как Perl, Tcl и Ruby.
Мой опыт работы с SWIG в целом положительный, поскольку он относительно прост в использовании и довольно мощный. Его часто использовали до того, как я стал более опытным в написании C-расширений. Однако написание пользовательских интерфейсов с помощью SWIG часто вызывает проблемы, потому что это должно делаться с использованием концепции typemaps, которые не специфичны для 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 и C-кода, подобного Fortran, в Python с поддержкой массивов Numeric. Он был написан Полом Дюбуа, выдающимся учёным в области компьютерных наук и первым сопровождающим Numeric (ныне на пенсии). Стоит упомянуть в надежде, что кто-то обновит PyFort для работы с массивами NumPy, которые теперь поддерживают массивы с непрерывной структурой как в стиле Fortran, так и в стиле C.