Как расширить NumPy
Написание модуля расширения
Хотя объект ndarray разработан для обеспечения быстрых вычислений в Python, он также предназначен для универсального использования и удовлетворения широкого спектра вычислительных потребностей. В результате, если абсолютная скорость имеет решающее значение, нет замены хорошо написанной, компилируемой циклической конструкции, специфичной для вашего приложения и оборудования. Это одна из причин, по которой numpy включает f2py, обеспечивая удобный механизм для связывания (простого) C/C++ и (произвольного) Fortran-кода непосредственно с Python. Рекомендуется использовать и улучшать этот механизм. Цель данного раздела — не документировать этот инструмент, а документировать более базовые шаги по написанию модуля расширения, от которого этот инструмент зависит.
После написания, компиляции и установки модуля расширения в какой-либо части пути Python (sys.path), код можно импортировать в Python, как если бы это был стандартный Python-файл. Он будет содержать объекты и методы, определенные и скомпилированные на C. Базовые шаги по выполнению этого в Python хорошо задокументированы, и вы можете найти больше информации в документации самого Python, доступной в интернете по адресу www.python.org .
Помимо Python C-API, существует полный и богатый C-API для NumPy, позволяющий выполнять сложные манипуляции на уровне C. Однако для большинства приложений обычно используется всего несколько вызовов API. Например, если вам нужно просто извлечь указатель на память вместе с некоторыми сведениями о форме для передачи в другую вычислительную процедуру, вы будете использовать совершенно другие вызовы, чем если вы пытаетесь создать новый тип, похожий на массив, или добавить новый тип данных для ndarray. В этом разделе описываются наиболее часто используемые вызовы API и макросы.
Необходимая подпрограмма
Существует ровно одна функция, которая должна быть определена в вашем C-коде, чтобы Python мог использовать ее в качестве модуля расширения. Функция должна называться init{имя}, где {имя} — это имя модуля из Python. Эта функция должна быть объявлена таким образом, чтобы быть видимой для кода за пределами этой процедуры. Помимо добавления желаемых методов и констант, эта подпрограмма также должна содержать вызовы, такие как import_array() и/или import_ufunc(), в зависимости от используемого C-API. Если забыть разместить эти команды, то это проявится как неприятный сбой сегментации (падение) как только любая подпрограмма C-API будет фактически вызвана. На самом деле возможно иметь несколько функций init{имя} в одном файле, в этом случае этот файл будет определять несколько модулей. Однако существуют некоторые хитрости, чтобы сделать это правильно, и они здесь не рассматриваются.
Минимальный init{name} метод выглядит следующим образом:
PyMODINIT_FUNC
init{name}(void)
{
(void)Py_InitModule({name}, mymethods);
import_array();
}
mymethods должен быть массивом (обычно статически объявленным) структур PyMethodDef, которые содержат имена методов, фактические C-функции, переменную, указывающую, использует ли метод ключевые аргументы, и строки документации. Они описаны в следующем разделе. Если вы хотите добавить константы в модуль, сохраните возвращаемое значение из Py_InitModule, которое является объектом модуля. Наиболее общий способ добавления элементов в модуль — получить словарь модуля с помощью PyModule_GetDict(module). С помощью словаря модуля вы можете вручную добавить все, что вам нужно, в модуль. Более простой способ добавления объектов в модуль — использовать один из трех дополнительных вызовов Python C-API, которые не требуют отдельного извлечения словаря модуля. Они задокументированы в документации Python, но приведены здесь для удобства:
- intPyModule_AddObject(PyObject*module, char*name, PyObject*value)
- intPyModule_AddIntConstant(PyObject*module, char*name, longvalue)
- intPyModule_AddStringConstant(PyObject*module, char*name, char*value)
-
Все эти три функции требуют объекта module (возвращаемое значение Py_InitModule). name — это строка, которая маркирует значение в модуле. В зависимости от вызываемой функции, аргумент value является либо общим объектом (
PyModule_AddObjectзаимствует ссылку на него), либо целочисленной константой, либо строковой константой.
Определение функций
Вторым аргументом, передаваемым в функцию Py_InitModule, является структура, которая упрощает определение функций в модуле. В приведенном выше примере структура mymethods была определена ранее в файле (обычно непосредственно перед подпрограммой init{name}) для:
static PyMethodDef mymethods[] = {
{ nokeywordfunc,nokeyword_cfunc,
METH_VARARGS,
Doc string},
{ keywordfunc, keyword_cfunc,
METH_VARARGS|METH_KEYWORDS,
Doc string},
{NULL, NULL, 0, NULL} /* Sentinel */
}
Каждый элемент массива mymethods представляет собой структуру PyMethodDef, содержащую 1) имя Python, 2) функцию C, реализующую функцию, 3) флаги, указывающие, принимаются ли ключевые слова для этой функции, и 4) строку документации для функции. Для одного модуля может быть определено любое количество функций путем добавления дополнительных элементов в эту таблицу. Последний элемент должен быть полностью NULL, как показано, чтобы действовать как стоп-сигнал. Python ищет этот элемент, чтобы узнать, что все функции модуля были определены.
Последним шагом по завершению модуля расширения является написание кода, который выполняет необходимые функции. Существует два типа функций: те, которые не принимают ключевые аргументы, и те, которые принимают.
Функции без ключевых аргументов
Функции, которые не принимают ключевые аргументы, должны быть написаны так:
static PyObject*
nokeyword_cfunc (PyObject *dummy, PyObject *args)
{
/* convert Python arguments */
/* do function */
/* return something */
}
Аргумент dummy в этом контексте не используется и может быть безопасно проигнорирован. Аргумент args содержит все аргументы, переданные функции в виде кортежа. Вы можете делать все, что угодно на этом этапе, но обычно самый простой способ управления входными аргументами — вызвать PyArg_ParseTuple (args, format_string, адреса_C_переменных…) или PyArg_UnpackTuple (кортеж, “имя”, мин, макс, …). Хорошее описание того, как использовать первую функцию, содержится в справочнике по Python C-API в разделе 5.5 (Разбор аргументов и построение значений). Обратите особое внимание на формат “O&”, который использует функции преобразования для перехода между объектом Python и объектом C. Все остальные функции форматирования можно (по большей части) рассматривать как частные случаи этого общего правила. В NumPy C-API определены несколько функций преобразования, которые могут быть полезны. В частности, функция PyArray_DescrConverter очень полезна для поддержки произвольного задания типов данных. Эта функция преобразует любой допустимый объект типа данных Python в объект PyArray_Descr*.
В исходном коде NumPy есть множество примеров использования PyArg_ParseTuple. Стандартное использование выглядит так:
PyObject *input;
PyArray_Descr *dtype;
if (!PyArg_ParseTuple(args, "OO&", &input,
PyArray_DescrConverter,
&dtype)) return NULL;
Важно помнить, что при использовании строки формата “O” вы получаете заимствованную ссылку на объект. Однако функции преобразования обычно требуют некоторой обработки памяти. В этом примере, если преобразование успешно, dtype будет содержать новую ссылку на объект PyArray_Descr*, а input — заимствованную ссылку. Поэтому, если это преобразование было бы смешано с другим преобразованием (скажем, в целое число) и преобразование типа данных прошло успешно, но преобразование целого числа потерпело неудачу, то вам нужно будет освободить счетчик ссылок на объект типа данных перед возвращением. Типичный способ сделать это — установить dtype в NULL перед вызовом PyArg_ParseTuple, а затем использовать Py_XDECREF для dtype перед возвращением.
После обработки входных аргументов пишется код, который фактически выполняет работу (вероятно, вызывая другие функции по мере необходимости). Последним этапом функции C является возврат чего-либо. Если возникла ошибка, то необходимо вернуть NULL (убедившись, что ошибка действительно установлена). Если ничего не должно быть возвращено, то увеличьте счетчик Py_None и верните его. Если должен быть возвращен один объект, то он возвращается (убедившись, что у вас есть ссылка на него). Если должны быть возвращены несколько объектов, то нужно вернуть кортеж. Функция Py_BuildValue (строка_формата, c_переменные…) упрощает построение кортежей объектов Python из переменных C. Обратите особое внимание на разницу между ‘N’ и ‘O’ в строке формата, иначе вы можете легко создать утечки памяти. Строка формата ‘O’ увеличивает счетчик ссылок на переменную C PyObject*, а строка формата ‘N’ захватывает ссылку на соответствующую переменную C PyObject*. Вы должны использовать ‘N’, если вы уже создали ссылку на объект и просто хотите предоставить эту ссылку кортежу. Вы должны использовать ‘O’, если у вас есть только заимствованная ссылка на объект и вам нужно создать новую, чтобы предоставить её кортежу.
Функции с ключевыми аргументами
Эти функции очень похожи на функции без ключевых аргументов. Единственное отличие в том, что сигнатура функции выглядит так:
static PyObject*
keyword_cfunc (PyObject *dummy, PyObject *args, PyObject *kwds)
{
...
}
Аргумент kwds содержит словарь Python, ключами которого являются имена ключевых аргументов, а значениями — соответствующие значения ключевых аргументов. Этот словарь можно обработать любым удобным для вас способом. Однако самый простой способ обработки — заменить функцию PyArg_ParseTuple (args, строка_формата, адреса…) вызовом PyArg_ParseTupleAndKeywords (args, kwds, строка_формата, массив_строк_kwlist[], адреса…). Параметр kwlist этой функции представляет собой массив строк, завершающийся NULL и предоставляющий ожидаемые ключевые аргументы. Должна быть одна строка для каждого элемента в строке формата. При использовании этой функции будет поднята ошибка TypeError, если переданы недопустимые ключевые аргументы.
Дополнительную помощь по этой функции вы найдете в разделе 1.8 (Ключевые параметры для функций расширения) руководства по расширению и встраиванию в документации Python.
Счётчик ссылок
Самая большая сложность при написании модулей расширения — это счётчик ссылок. Это важная причина популярности f2py, weave, Cython, ctypes и т. д. Если вы неправильно обработаете счётчики ссылок, могут возникнуть проблемы, от утечек памяти до ошибок сегментации. Единственная стратегия, которую я знаю для корректной обработки счётчиков ссылок, — это труд, упорство и усилия. Сначала вы заставляете себя понять, что каждая переменная Python имеет счётчик ссылок. Затем вы точно понимаете, что делает каждая функция со счётчиком ссылок ваших объектов, чтобы правильно использовать DECREF и INCREF, когда это необходимо. Счётчик ссылок действительно может проверить ваш уровень терпения и старания в программировании. Несмотря на мрачную картину, в большинстве случаев счётчик ссылок довольно прост, а самой распространённой трудностью является то, что вы не используете DECREF для объектов перед преждевременным выходом из процедуры из-за какой-либо ошибки. На втором месте стоит распространённая ошибка — отсутствие владения ссылкой на объект, который передаётся в функцию или макрос, который собирается её украсть ( например PyTuple_SET_ITEM, и большинство функций, принимающих PyArray_Descr объекты).
Обычно вы получаете новую ссылку на переменную при её создании или в качестве результата выполнения некоторой функции (однако существуют некоторые заметные исключения — такие как получение элемента из кортежа или словаря). Когда вы владеете ссылкой, вы несёте ответственность за то, чтобы Py_DECREF (переменная) вызывался, когда переменная больше не нужна (и ни одна другая функция не «украла» её ссылку). Кроме того, если вы передаёте Python-объект функции, которая «украдёт» ссылку, убедитесь, что вы владеете ею (или используйте Py_INCREF чтобы получить свою собственную ссылку). Вы также встретите понятие заимствования ссылки. Функция, которая заимствует ссылку, не изменяет счётчик ссылок объекта и не ожидает «удержать» ссылку. Она просто временно использует объект. При использовании PyArg_ParseTuple или PyArg_UnpackTuple вы получаете заимствованную ссылку на объекты в кортеже и не должны изменять их счётчик ссылок внутри вашей функции. С практикой вы сможете правильно обработать счётчик ссылок, но вначале это может быть сложно.
Одним из распространённых источников ошибок счёта ссылок является функция Py_BuildValue. Внимательно обратите внимание на разницу между символом формата ‘N’ и символом формата ‘O’. Если вы создаёте новый объект в вашей подпрограмме (например, выходной массив), и вы передаёте его обратно в кортеже возвращаемых значений, скорее всего, вам следует использовать символ формата ‘N’ в Py_BuildValue. Символ ‘O’ увеличит счётчик ссылок на единицу. Это оставит вызывающую программу с двумя счётчиками ссылок для нового массива. Когда переменная удаляется, а счётчик ссылок уменьшается на единицу, всё ещё будет этот дополнительный счётчик ссылок, и массив никогда не будет освобождён. У вас будет утечка памяти, вызванная счётчиком ссылок. Использование символа ‘N’ позволит избежать этой ситуации, так как он вернёт вызывающей программе объект (внутри кортежа) с единственным счётчиком ссылок.
Работа с объектами массивов
Большинство модулей расширения NumPy потребуют доступа к памяти объекта ndarray (или одного из его подклассов). Самый простой способ сделать это не требует глубокого знания внутренней работы NumPy.
-
Убедитесь, что вы работаете с хорошо себя ведущим массивом (выровненным, в порядке байтов машины и односегментным) правильного типа и количества измерений.
- Преобразуя его из некоторого Python-объекта с помощью
PyArray_FromAnyили построенного на нём макроса. - Создавая новый ndarray нужной формы и типа с помощью
PyArray_NewFromDescrили более простого макроса или функции, основанной на нём.
- Преобразуя его из некоторого Python-объекта с помощью
- Получите форму массива и указатель на его фактические данные.
- Передайте данные и информацию о форме подпрограмме или другой части кода, которая фактически выполняет вычисления.
- Если вы пишете алгоритм, я рекомендую использовать информацию о шаге, содержащуюся в массиве, для доступа к элементам массива (макросы
PyArray_GetPtrделают это безболезненно). Затем вы можете ослабить свои требования, чтобы не требовать односегментный массив и копирование данных, которое может возникнуть.
Каждый из этих подпунктов рассматривается в следующих подразделах.
Преобразование произвольного последовательного объекта
Основная процедура получения массива из любого Python-объекта, который можно преобразовать в массив, — это PyArray_FromAny. Эта функция очень гибкая с множеством входных аргументов. Несколько макросов делают использование базовой функции проще. PyArray_FROM_OTF, пожалуй, самый полезный из этих макросов для наиболее распространённых случаев. Он позволяет преобразовать произвольный Python-объект в массив определённого встроенного типа данных ( например, float), при этом определяя определённый набор требований ( например, непрерывный, выровненный и доступный для записи). Синтаксис
PyArray_FROM_OTF-
Возвращает ndarray из любого объекта Python, obj, который может быть преобразован в массив. Количество измерений в возвращаемом массиве определяется объектом. Желаемый тип данных возвращаемого массива задаётся в typenum, который должен быть одним из перечисленных типов. Требования к возвращаемому массиву могут быть любым сочетанием стандартных флагов массива. Каждый из этих аргументов поясняется более подробно ниже. Вы получаете новую ссылку на массив при успешном выполнении. В случае неудачи возвращается
NULLи устанавливается исключение.- obj
-
Объект может быть любым объектом Python, преобразуемым в ndarray. Если объект уже является (подклассом) ndarray, удовлетворяющим требованиям, то возвращается новая ссылка. В противном случае создаётся новый массив. Содержимое obj копируется в новый массив, если не используется интерфейс массива, чтобы данные не приходилось копировать. Объекты, которые могут быть преобразованы в массив, включают: 1) любой вложенный последовательный объект, 2) любой объект, использующий интерфейс массива, 3) любой объект с методом
__array__(который должен возвращать ndarray), и 4) любой скалярный объект (превращается в массив нулевой размерности). Подклассы ndarray, которые в противном случае удовлетворяют требованиям, будут переданы без изменений. Если вы хотите гарантировать базовый массив ndarray, используйтеNPY_ARRAY_ENSUREARRAYв флаге требований. Копия создаётся только при необходимости. Если вы хотите гарантировать копирование, передайтеNPY_ARRAY_ENSURECOPYв флаг требований. - typenum
-
Один из перечисленных типов или
NPY_NOTYPE, если тип данных должен определяться самим объектом. Можно использовать C-базовые имена:NPY_BOOL,NPY_BYTE,NPY_UBYTE,NPY_SHORT,NPY_USHORT,NPY_INT,NPY_UINT,NPY_LONG,NPY_ULONG,NPY_LONGLONG,NPY_ULONGLONG,NPY_DOUBLE,NPY_LONGDOUBLE,NPY_CFLOAT,NPY_CDOUBLE,NPY_CLONGDOUBLE,NPY_OBJECT.В качестве альтернативы, можно использовать имена с битовой шириной, поддерживаемые платформой. Например:
NPY_INT8,NPY_INT16,NPY_INT32,NPY_INT64,NPY_UINT8,NPY_UINT16,NPY_UINT32,NPY_UINT64,NPY_FLOAT32,NPY_FLOAT64,NPY_COMPLEX64,NPY_COMPLEX128.Объект будет преобразован к нужному типу только если это возможно без потери точности. В противном случае возвращается
NULLи возникает ошибка. ИспользуйтеNPY_ARRAY_FORCECASTв флаге требований, чтобы переопределить это поведение. - requirements
-
Модель памяти для ndarray допускает произвольные шаги в каждом измерении для перехода к следующему элементу массива. Однако часто необходимо взаимодействовать с кодом, который ожидает C-непрерывный или Fortran-непрерывный макет памяти. Кроме того, ndarray может быть невыровнен (адрес элемента не находится на целое кратное размера элемента), что может привести к сбою программы (или, по крайней мере, к более медленной работе), если вы попытаетесь обратиться к указателю в данные массива. Обе эти проблемы могут быть решены путём преобразования объекта Python в массив, который более «удобен» для вашего конкретного использования.
Флаг требований позволяет указать, какой тип массива приемлем. Если переданный объект не удовлетворяет этим требованиям, то создаётся копия, чтобы возвращаемый объект удовлетворял требованиям. Эти ndarray могут использовать очень общий указатель на память. Этот флаг позволяет указать желаемые свойства возвращаемого объекта массива. Все флаги объясняются в главе подробного API. Наиболее часто используемые флаги —
NPY_ARRAY_IN_ARRAY,NPY_ARRAY_OUT_ARRAYиNPY_ARRAY_INOUT_ARRAY:NPY_ARRAY_IN_ARRAY-
Этот флаг полезен для массивов, которые должны быть C-непрерывными и выровненными. Такие массивы обычно являются входными массивами для некоторого алгоритма.
NPY_ARRAY_OUT_ARRAY-
Этот флаг полезен для задания массива, который является C-непрерывным, выровненным и в который можно записывать. Такой массив обычно возвращается в качестве выходного (хотя обычно такие выходные массивы создаются заново).
NPY_ARRAY_INOUT_ARRAY-
Этот флаг полезен для задания массива, который будет использоваться как для входных, так и для выходных данных.
PyArray_ResolveWritebackIfCopyдолжен быть вызван передPy_DECREFв конце процедуры интерфейса, чтобы записать временные данные обратно в исходный массив, переданный в качестве входного. Использование флагаNPY_ARRAY_WRITEBACKIFCOPYтребует, чтобы входной объект уже был массивом (потому что другие объекты не могут быть автоматически обновлены таким образом). Если произошла ошибка, используйтеPyArray_DiscardWritebackIfCopy(obj) для массива с установленными этими флагами. Это сделает базовой массив доступным для записи без копирования содержимого обратно в исходный массив.
Другие полезные флаги, которые можно объединять как дополнительные требования, включают:
NPY_ARRAY_FORCECAST-
Преобразование к нужному типу, даже если это невозможно без потери информации.
NPY_ARRAY_ENSURECOPY-
Убедитесь, что полученный массив является копией оригинала.
NPY_ARRAY_ENSUREARRAY-
Убедитесь, что полученный объект является фактически ndarray, а не подклассом.
Примечание
То, является ли массив байт-переставленным, определяется типом данных массива. Массивы с родным порядком байтов всегда запрашиваются функцией PyArray_FROM_OTF, поэтому флаг NPY_ARRAY_NOTSWAPPED в аргументе требований не нужен. Также нет способа получить байт-переставленный массив с помощью этой функции.
Создание нового ndarray
Часто в коде расширений требуется создать новые массивы. Возможно, нужен выходной массив, и вы не хотите, чтобы вызывающая сторона его предоставляла. Возможно, нужен только временный массив для промежуточного вычисления. В любом случае есть простые способы получения объекта ndarray нужного типа данных. Наиболее универсальная функция для этого — PyArray_NewFromDescr. Все функции создания массивов используют этот многократно используемый код. Из-за своей гибкости его использование может быть немного сложным. В результате существуют более простые формы, которые легче использовать. Эти формы являются частью семейства функций PyArray_SimpleNew, которые упрощают интерфейс, предоставляя значения по умолчанию для распространенных случаев использования.
Доступ к памяти ndarray и чтение элементов ndarray
Если obj является ndarray (PyArrayObject*), то область данных ndarray указана указателем void* PyArray_DATA (obj) или указателем char* PyArray_BYTES (obj). Помните, что (вообще говоря) эта область данных может не быть выровнена в соответствии с типом данных, она может представлять данные с переставленными байтами и/или она может быть не доступной для записи. Если область данных выровнена и имеет родной порядок байтов, то способ получения конкретного элемента массива определяется только массивом переменных npy_intp PyArray_STRIDES (obj). В частности, этот массив целых чисел показывает, сколько байтов необходимо добавить к текущему указателю элемента, чтобы получить следующий элемент в каждом измерении. Для массивов меньше 4 измерений существуют макросы PyArray_GETPTR{k} (obj, …), где {k} — целые числа 1, 2, 3 или 4, которые облегчают использование шагов массива. Аргументы … представляют {k} неотрицательных целочисленных индексов в массиве. Например, предположим, что E — это 3-мерный массив ndarray. Указатель (void*) на элемент E[i,j,k] получается как PyArray_GETPTR3 (E, i, j, k).
Как объяснялось ранее, массивы с непрерывной структурой по стилю C и непрерывной структурой по стилю Fortran имеют особые шаблоны шагов. Два флага массива (NPY_ARRAY_C_CONTIGUOUS и NPY_ARRAY_F_CONTIGUOUS) указывают, соответствует ли шаблон шагов данного массива непрерывной структуре по стилю C или Fortran, или ни одному из них. Соответствие шаблона шагов стандартному шаблону C или Fortran можно проверить с помощью PyArray_IS_C_CONTIGUOUS (obj) и PyArray_ISFORTRAN (obj) соответственно. Большинство сторонних библиотек ожидают непрерывные массивы. Но часто несложно поддерживать общие шаги. Я рекомендую использовать информацию о шагах в вашем собственном коде по возможности и оставлять требования к единственному сегменту для обертывания стороннего кода. Использование информации о шагах, предоставляемой с ndarray, а не требование непрерывного шага уменьшает копирование, которое в противном случае необходимо было бы выполнить.
Пример
Следующий пример показывает, как вы можете написать обертку, которая принимает два входных аргумента (которые будут преобразованы в массив) и выходной аргумент (который должен быть массивом). Функция возвращает None и обновляет выходной массив. Обратите внимание на обновленное использование семантики WRITEBACKIFCOPY для NumPy v1.14 и выше
static PyObject *
example_wrapper(PyObject *dummy, PyObject *args)
{
PyObject *arg1=NULL, *arg2=NULL, *out=NULL;
PyObject *arr1=NULL, *arr2=NULL, *oarr=NULL;
if (!PyArg_ParseTuple(args, "OOO!", &arg1, &arg2,
&PyArray_Type, &out)) return NULL;
arr1 = PyArray_FROM_OTF(arg1, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
if (arr1 == NULL) return NULL;
arr2 = PyArray_FROM_OTF(arg2, NPY_DOUBLE, NPY_ARRAY_IN_ARRAY);
if (arr2 == NULL) goto fail;
#if NPY_API_VERSION >= 0x0000000c
oarr = PyArray_FROM_OTF(out, NPY_DOUBLE, NPY_ARRAY_INOUT_ARRAY2);
#else
oarr = PyArray_FROM_OTF(out, NPY_DOUBLE, NPY_ARRAY_INOUT_ARRAY);
#endif
if (oarr == NULL) goto fail;
/* code that makes use of arguments */
/* You will probably need at least
nd = PyArray_NDIM(<..>) -- number of dimensions
dims = PyArray_DIMS(<..>) -- npy_intp array of length nd
showing length in each dim.
dptr = (double *)PyArray_DATA(<..>) -- pointer to data.
If an error occurs goto fail.
*/
Py_DECREF(arr1);
Py_DECREF(arr2);
#if NPY_API_VERSION >= 0x0000000c
PyArray_ResolveWritebackIfCopy(oarr);
#endif
Py_DECREF(oarr);
Py_INCREF(Py_None);
return Py_None;
fail:
Py_XDECREF(arr1);
Py_XDECREF(arr2);
#if NPY_API_VERSION >= 0x0000000c
PyArray_DiscardWritebackIfCopy(oarr);
#endif
Py_XDECREF(oarr);
return NULL;
}
© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/user/c-info.how-to-extend.html