Как расширить 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{name}, где {name} — имя модуля из Python. Эта функция должна быть объявлена таким образом, чтобы она была видимой для кода, находящегося за пределами этой подпрограммы. Помимо добавления желаемых методов и констант, эта подпрограмма также должна содержать вызовы, такие как import_array() и/или import_ufunc(), в зависимости от используемого C-API. Если забыть разместить эти команды, это проявится как некрасивая ошибка сегментации (сбой) как только любая подпрограмма C-API будет фактически вызвана. Фактически возможно иметь несколько функций init{name} в одном файле, в котором случае этот файл будет определять несколько модулей. Однако есть некоторые хитрости, чтобы заставить это работать правильно, и это не рассматривается здесь.
Минимальный метод 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 (кортеж, “имя”, min, max, …). Хорошее описание того, как использовать первую функцию, содержится в справочнике Python C-API в разделе 5.5 (Разбор аргументов и создание значений). Вы должны уделить особое внимание формату “O&”, который использует функции преобразования для перехода между объектом Python и объектом C. Все другие функции формата можно (в основном) рассматривать как частные случаи этого общего правила. В NumPy C-API определены несколько функций преобразования, которые могут быть полезными. В частности, функция PyArray_DescrConverter очень полезна для поддержки произвольного указания типа данных. Эта функция преобразует любой допустимый объект типа данных Python в объект PyArray_Descr*. Не забудьте передать адрес C-переменных, которые должны быть заполнены.
В коде 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 (format_string, c_variables…) облегчает создание кортежей объектов 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, format_string, addresses…) вызовом функции PyArg_ParseTupleAndKeywords (args, kwds, format_string, char *kwlist[], addresses…). Параметр kwlist этой функции является NULL -завершённым массивом строк, представляющим ожидаемые ключевые аргументы. Должна быть одна строка для каждого элемента в строке format_string. Использование этой функции вызовет TypeError, если будут переданы неверные ключевые аргументы.
Для получения дополнительной информации об этой функции см. раздел 1.8 (Ключевые параметры для функций расширения) руководства по расширению и внедрению в документации Python.
Счётчик ссылок
Самая большая сложность при написании модулей расширения — счётчик ссылок. Это важная причина популярности f2py, weave, Cython, ctypes и т. д…. Если неправильно обращаться со счётчиком ссылок, могут возникнуть проблемы, от утечек памяти до ошибок сегментации. Единственная стратегия, которую я знаю для правильного обращения со счётчиком ссылок, — это упорный труд. Во-первых, необходимо осознать, что у каждой переменной Python есть счётчик ссылок. Затем необходимо точно понимать, что делает каждая функция со счётчиком ссылок ваших объектов, чтобы правильно использовать DECREF и INCREF при необходимости. Счётчик ссылок может действительно проверить количество терпения и прилежания, которые вы проявляете в программировании. Несмотря на мрачную картину, большинство случаев работы со счётчиком ссылок довольно просты, а наиболее распространённая проблема заключается в том, что вы не используете DECREF для объектов перед преждевременным выходом из процедуры из-за какой-либо ошибки. На втором месте — распространённая ошибка, заключающаяся в том, что вы не являетесь владельцем ссылки на объект, который передаётся функции или макросу, который собирается заимствовать ссылку (например, PyTuple_SET_ITEM и большинство функций, которые принимают PyArray_Descr объекты).
Обычно вы получаете новую ссылку на переменную при её создании или при возвращении её значением какой-либо функции (существуют, однако, некоторые заметные исключения, такие как получение элемента из кортежа или словаря). Когда вы владеете ссылкой, вы несёте ответственность за то, чтобы убедиться, что Py_DECREF (var) вызывается, когда переменная больше не нужна (и ни одна другая функция не «украла» её ссылку). Кроме того, если вы передаёте объект 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 в массив, который будет более «податлив» для вашего конкретного использования.
Флаг requirements позволяет указать, какой тип массива приемлем. Если переданный объект не соответствует этим требованиям, то создается копия, чтобы возвращаемый объект соответствовал требованиям. Эти ndarray могут использовать очень общий указатель на память. Этот флаг позволяет указать желаемые свойства возвращаемого объекта массива. Все флаги подробно описаны в главе API. Наиболее часто используемые флаги —
NPY_ARRAY_IN_ARRAY,NPY_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илиNPY_ARRAY_UPDATEIFCOPYтребует, чтобы входной объект уже был массивом (потому что другие объекты не могут быть автоматически обновлены таким образом). Если возникает ошибка, используйтеPyArray_DiscardWritebackIfCopy(obj) для массива с установленными этими флагами. Это сделает базовый массив под ним доступным для записи, не копируя содержимое обратно в исходный массив.
Другие полезные флаги, которые можно объединять как дополнительные требования, это:
NPY_ARRAY_FORCECAST-
Преобразование к требуемому типу, даже если это невозможно без потери информации.
NPY_ARRAY_ENSURECOPY-
Обеспечение того, что результирующий массив является копией оригинального.
NPY_ARRAY_ENSUREARRAY-
Убедитесь, что результирующий объект является фактическим ndarray, а не подклассом.
Примечание
Является ли массив байтовым, определяется типом данных массива. Массивы с родным порядком байтов всегда запрашиваются с помощью PyArray_FROM_OTF, поэтому нет необходимости в флаге NPY_ARRAY_NOTSWAPPED в аргументе requirements. Также нет способа получить байтовый массив с обратным порядком байтов с помощью этой функции.
Создание нового 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 является трёхмерным 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 версии 1.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–2022 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.21/user/c-info.how-to-extend.html