API итератора массивов
Новая версия с 1.6.
Итератор массивов
Итератор массивов объединяет ключевые особенности ufuncs, позволяя коду пользователя поддерживать такие функции, как параметры вывода, сохранение структуры памяти и буферизацию данных с неправильным выравниванием или типом без сложных кодировок.
На этой странице описывается API итератора. Итератор назван NpyIter , а функции названы NpyIter_*.
Существует вводный справочник по итерации массивов, который может быть интересен тем, кто использует этот API на C. Во многих случаях стоит сначала протестировать идеи, создав итератор в Python, прежде чем писать код итерации на C.
Пример простой итерации
Лучший способ ознакомиться с итератором — посмотреть его использование в самом коде NumPy. Например, вот немного изменённая версия кода для PyArray_CountNonzero, который считает количество ненулевых элементов в массиве.
npy_intp PyArray_CountNonzero(PyArrayObject* self)
{
/* Nonzero boolean function */
PyArray_NonzeroFunc* nonzero = PyArray_DESCR(self)->f->nonzero;
NpyIter* iter;
NpyIter_IterNextFunc *iternext;
char** dataptr;
npy_intp nonzero_count;
npy_intp* strideptr,* innersizeptr;
/* Handle zero-sized arrays specially */
if (PyArray_SIZE(self) == 0) {
return 0;
}
/*
* Create and use an iterator to count the nonzeros.
* flag NPY_ITER_READONLY
* - The array is never written to.
* flag NPY_ITER_EXTERNAL_LOOP
* - Inner loop is done outside the iterator for efficiency.
* flag NPY_ITER_NPY_ITER_REFS_OK
* - Reference types are acceptable.
* order NPY_KEEPORDER
* - Visit elements in memory order, regardless of strides.
* This is good for performance when the specific order
* elements are visited is unimportant.
* casting NPY_NO_CASTING
* - No casting is required for this operation.
*/
iter = NpyIter_New(self, NPY_ITER_READONLY|
NPY_ITER_EXTERNAL_LOOP|
NPY_ITER_REFS_OK,
NPY_KEEPORDER, NPY_NO_CASTING,
NULL);
if (iter == NULL) {
return -1;
}
/*
* The iternext function gets stored in a local variable
* so it can be called repeatedly in an efficient manner.
*/
iternext = NpyIter_GetIterNext(iter, NULL);
if (iternext == NULL) {
NpyIter_Deallocate(iter);
return -1;
}
/* The location of the data pointer which the iterator may update */
dataptr = NpyIter_GetDataPtrArray(iter);
/* The location of the stride which the iterator may update */
strideptr = NpyIter_GetInnerStrideArray(iter);
/* The location of the inner loop size which the iterator may update */
innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
nonzero_count = 0;
do {
/* Get the inner loop data/stride/count values */
char* data = *dataptr;
npy_intp stride = *strideptr;
npy_intp count = *innersizeptr;
/* This is a typical inner loop for NPY_ITER_EXTERNAL_LOOP */
while (count--) {
if (nonzero(data, self)) {
++nonzero_count;
}
data += stride;
}
/* Increment the iterator to the next inner loop */
} while(iternext(iter));
NpyIter_Deallocate(iter);
return nonzero_count;
}
Пример простой многократной итерации
Вот функция копирования, использующая итератор. Параметр order используется для управления структурой памяти результата, обычно требуется NPY_KEEPORDER.
PyObject *CopyArray(PyObject *arr, NPY_ORDER order)
{
NpyIter *iter;
NpyIter_IterNextFunc *iternext;
PyObject *op[2], *ret;
npy_uint32 flags;
npy_uint32 op_flags[2];
npy_intp itemsize, *innersizeptr, innerstride;
char **dataptrarray;
/*
* No inner iteration - inner loop is handled by CopyArray code
*/
flags = NPY_ITER_EXTERNAL_LOOP;
/*
* Tell the constructor to automatically allocate the output.
* The data type of the output will match that of the input.
*/
op[0] = arr;
op[1] = NULL;
op_flags[0] = NPY_ITER_READONLY;
op_flags[1] = NPY_ITER_WRITEONLY | NPY_ITER_ALLOCATE;
/* Construct the iterator */
iter = NpyIter_MultiNew(2, op, flags, order, NPY_NO_CASTING,
op_flags, NULL);
if (iter == NULL) {
return NULL;
}
/*
* Make a copy of the iternext function pointer and
* a few other variables the inner loop needs.
*/
iternext = NpyIter_GetIterNext(iter, NULL);
innerstride = NpyIter_GetInnerStrideArray(iter)[0];
itemsize = NpyIter_GetDescrArray(iter)[0]->elsize;
/*
* The inner loop size and data pointers may change during the
* loop, so just cache the addresses.
*/
innersizeptr = NpyIter_GetInnerLoopSizePtr(iter);
dataptrarray = NpyIter_GetDataPtrArray(iter);
/*
* Note that because the iterator allocated the output,
* it matches the iteration order and is packed tightly,
* so we don't need to check it like the input.
*/
if (innerstride == itemsize) {
do {
memcpy(dataptrarray[1], dataptrarray[0],
itemsize * (*innersizeptr));
} while (iternext(iter));
} else {
/* For efficiency, should specialize this based on item size... */
npy_intp i;
do {
npy_intp size = *innersizeptr;
char *src = dataptrarray[0], *dst = dataptrarray[1];
for(i = 0; i < size; i++, src += innerstride, dst += itemsize) {
memcpy(dst, src, itemsize);
}
} while (iternext(iter));
}
/* Get the result from the iterator object array */
ret = NpyIter_GetOperandArray(iter)[1];
Py_INCREF(ret);
if (NpyIter_Deallocate(iter) != NPY_SUCCEED) {
Py_DECREF(ret);
return NULL;
}
return ret;
}
Типы данных итератора
Структура итератора — внутренняя деталь, и код пользователя видит только неполную структуру.
-
NpyIter -
Это указатель на непрозрачный тип итератора. Доступ к его содержимому возможен только через API итератора.
-
NpyIter_Type -
Это тип, который предоставляет итератор Python. В настоящее время не предоставляется API, который обеспечивает доступ к значениям созданного в Python итератора. Если итератор создаётся в Python, он должен использоваться в Python, и наоборот. Такой API, вероятно, будет создан в будущей версии.
-
NpyIter_IterNextFunc -
Это указатель на функцию для цикла итерации, возвращаемый
NpyIter_GetIterNext.
-
NpyIter_GetMultiIndexFunc -
Это указатель на функцию для получения текущего многомерного индекса итератора, возвращаемый
NpyIter_GetGetMultiIndex.
Создание и уничтожение
-
NpyIter* NpyIter_New(PyArrayObject* op, npy_uint32 flags, NPY_ORDER order, NPY_CASTING casting, PyArray_Descr* dtype) -
Создаёт итератор для заданного объекта массива NumPy
op.Флаги, которые могут быть переданы в
flags— любая комбинация глобальных и операторных флагов, описанных вNpyIter_MultiNew, за исключениемNPY_ITER_ALLOCATE.Любые значения перечисления
NPY_ORDERмогут быть переданы вorder. Для эффективной итерации лучшее значение —NPY_KEEPORDER, а другие значения навязывают определённый шаблон итерации.Любые значения перечисления
NPY_CASTINGмогут быть переданы вcasting. Значения включаютNPY_NO_CASTING,NPY_EQUIV_CASTING,NPY_SAFE_CASTING,NPY_SAME_KIND_CASTINGиNPY_UNSAFE_CASTING. Для разрешения преобразований также необходимо включить копирование или буферизацию.Если
dtypeнеNULL, то требуется тип данных. Если копирование разрешено, при необходимости преобразования типов будет создана временная копия. ЕслиNPY_ITER_UPDATEIFCOPYвключено, данные также будут скопированы обратно с другим преобразованием при уничтожении итератора.Возвращает NULL при ошибке, в противном случае возвращает выделенный итератор.
Для создания итератора, аналогичного старому итератору, это должно работать.
iter = NpyIter_New(op, NPY_ITER_READWRITE, NPY_CORDER, NPY_NO_CASTING, NULL);Если необходимо изменить массив с выровненным
doubleкодом, но порядок не имеет значения, используйте это.dtype = PyArray_DescrFromType(NPY_DOUBLE); iter = NpyIter_New(op, NPY_ITER_READWRITE| NPY_ITER_BUFFERED| NPY_ITER_NBO| NPY_ITER_ALIGNED, NPY_KEEPORDER, NPY_SAME_KIND_CASTING, dtype); Py_DECREF(dtype);
-
NpyIter* NpyIter_MultiNew(npy_intp nop, PyArrayObject** op, npy_uint32 flags, NPY_ORDER order, NPY_CASTING casting, npy_uint32* op_flags, PyArray_Descr** op_dtypes) -
Создаёт итератор для трансляции массивов
nopобъектов, указанных вop, с использованием стандартных правил трансляции NumPy.Любые значения перечисления
NPY_ORDERмогут быть переданы вorder. Для эффективной итерации наилучшим вариантом являетсяNPY_KEEPORDER, а другие порядки навязывают определённую схему итерации. При использованииNPY_KEEPORDER, для того чтобы гарантировать, что итерация не будет обращена вдоль оси, необходимо передать флагNPY_ITER_DONT_NEGATE_STRIDES.Любые значения перечисления
NPY_CASTINGмогут быть переданы вcasting. Значения включаютNPY_NO_CASTING,NPY_EQUIV_CASTING,NPY_SAFE_CASTING,NPY_SAME_KIND_CASTINGиNPY_UNSAFE_CASTING. Для того чтобы разрешить преобразования, также необходимо включить копирование или буферизацию.Если
op_dtypesнеNULL, оно определяет тип данных илиNULLдля каждогоop[i].Возвращает NULL, если произошла ошибка, иначе возвращает выделенный итератор.
Флаги, которые могут быть переданы в
flags, применимые ко всему итератору:-
NPY_ITER_C_INDEX -
Заставляет итератор отслеживать плоский индекс, соответствующий порядку C. Этот параметр нельзя использовать с
NPY_ITER_F_INDEX.
-
NPY_ITER_F_INDEX -
Заставляет итератор отслеживать плоский индекс, соответствующий порядку Fortran. Этот параметр нельзя использовать с
NPY_ITER_C_INDEX.
-
NPY_ITER_MULTI_INDEX -
Заставляет итератор отслеживать многомерный индекс. Это предотвращает объединение осей итератором для создания больших внутренних циклов. Если цикл также не буферизован, и не отслеживается никакой индекс (
NpyIter_RemoveAxisможет быть вызван), то размер итератора может быть-1для указания того, что итератор слишком большой. Это может произойти из-за сложной трансляции, и приведёт к ошибкам при установке диапазона итератора, удалении многомерного индекса или получении следующего элемента. Однако, можно снова удалить оси и использовать итератор в обычном режиме, если размер достаточно мал после удаления.
-
NPY_ITER_EXTERNAL_LOOP -
Заставляет итератор пропустить итерацию внутреннего цикла, потребовав от пользователя обработку внутреннего цикла.
Этот флаг несовместим с
NPY_ITER_C_INDEX,NPY_ITER_F_INDEXиNPY_ITER_MULTI_INDEX.
-
NPY_ITER_DONT_NEGATE_STRIDES -
Это влияет только на итератор, когда для параметра порядка указано
NPY_KEEPORDER. По умолчанию приNPY_KEEPORDERитератор меняет направление осей, имеющих отрицательные шаги, таким образом, что память просматривается вперёд. Этот флаг отключает этот этап. Используйте этот флаг, если вы хотите использовать базовое упорядочение памяти осей, но не хотите, чтобы ось меняла направление. Это поведение, например,numpy.ravel(a, order='K').
-
NPY_ITER_COMMON_DTYPE -
Заставляет итератор преобразовать все операнды в один общий тип данных, рассчитанный на основе правил повышения типа ufunc. Копирование или буферизация должны быть включены.
Если общий тип данных известен заранее, не используйте этот флаг. Вместо этого задайте требуемый тип данных для всех операндов.
-
NPY_ITER_REFS_OK -
Указывает, что массивы со ссылками (массивы объектов или структурированные массивы, содержащие тип объекта) могут быть приняты и использованы в итераторе. Если этот флаг включён, вызывающий должен убедиться, что
NpyIter_IterationNeedsAPI(iter)истинно, в противном случае он не может освободить GIL во время итерации.
-
NPY_ITER_ZEROSIZE_OK -
Указывает, что массивы с размером ноль должны быть разрешены. Поскольку типичный цикл итерации не работает естественным образом с массивами нулевого размера, необходимо проверить, что IterSize больше нуля, прежде чем входить в цикл итерации. В настоящее время проверяются только операнды, а не принудительная форма.
-
NPY_ITER_REDUCE_OK -
Разрешает доступные операнды с нулевым шагом размерности и размером, большим единицы. Обратите внимание, что такие операнды должны быть читаемыми/записываемыми.
При включённой буферизации это также переключает на специальный режим буферизации, который уменьшает длину цикла по мере необходимости, чтобы не перезаписывать значения, которые уменьшаются.
Обратите внимание, что если вы хотите выполнить сокращение на автоматически выделенном выводе, вы должны использовать
NpyIter_GetOperandArrayдля получения его ссылки, а затем установить каждое значение на единицу сокращения, прежде чем начать цикл итерации. В случае буферизованного сокращения это также означает, что необходимо указать флагNPY_ITER_DELAY_BUFALLOC, а затем сбросить итератор после инициализации выделенного операнда для подготовки буферов.
-
NPY_ITER_RANGED -
Включает поддержку итерации по поддиапазонам полного диапазона
iterindexдиапазона[0, NpyIter_IterSize(iter)). Используйте функциюNpyIter_ResetToIterIndexRangeдля указания диапазона итерации.Этот флаг может быть использован только с
NPY_ITER_EXTERNAL_LOOP, когдаNPY_ITER_BUFFEREDвключён. Это связано с тем, что без буферизации внутренний цикл всегда имеет размер самой внутренней размерности итерации, а разрешение его разделения потребовало бы специальной обработки, фактически сделав его более похожим на буферированный вариант.
-
NPY_ITER_BUFFERED -
Заставляет итератор сохранять данные буферизации и использовать буферизацию для удовлетворения требований к типу данных, выравниванию и порядку байтов. Для буферизации операнда не указывайте флаги
NPY_ITER_COPYилиNPY_ITER_UPDATEIFCOPY, потому что они переопределят буферизацию. Буферизация особенно полезна для кода Python, использующего итератор, позволяя обрабатывать большие объёмы данных за раз, чтобы амортизировать издержки интерпретатора Python.При использовании с
NPY_ITER_EXTERNAL_LOOPвнутренний цикл вызывающей стороны может получать более крупные фрагменты, чем это было бы возможно без буферизации, из-за того, как выстроены шаги.Обратите внимание, что если операнду задан флаг
NPY_ITER_COPYилиNPY_ITER_UPDATEIFCOPY, копия будет сделана вместо буферизации. Буферизация всё ещё произойдёт, если массив транслировался, поэтому элементы должны быть продублированы для получения постоянного шага.В обычной буферизации размер каждого внутреннего цикла равен размеру буфера или, возможно, больше, если указан
NPY_ITER_GROWINNER. ЕслиNPY_ITER_REDUCE_OKвключён и происходит сокращение, внутренние циклы могут стать меньше в зависимости от структуры сокращения.
-
NPY_ITER_GROWINNER -
При включённой буферизации это позволяет увеличить размер внутреннего цикла, когда буферизация не требуется. Этот параметр лучше всего использовать, если вы выполняете прямой проход по всем данным, а не с массивными данными для каждого внутреннего цикла.
-
NPY_ITER_DELAY_BUFALLOC -
При включённой буферизации это откладывает выделение буферов до вызова
NpyIter_Resetили другой функции сброса. Этот флаг существует, чтобы избежать ненужного копирования данных буфера при создании нескольких копий буферизованного итератора для многопоточной итерации.Другое использование этого флага — для настройки операций сокращения. После создания итератора и автоматического выделения выходного значения для сокращения итератором (убедитесь, что доступ READWRITE), его значение может быть инициализировано значением сокращения. Используйте
NpyIter_GetOperandArrayдля получения объекта. Затем вызовитеNpyIter_Resetдля выделения и заполнения буферов их начальными значениями.
Флаги, которые могут быть переданы в
op_flags[i], где0 <= i < nop:-
NPY_ITER_READWRITE
-
NPY_ITER_READONLY
-
NPY_ITER_WRITEONLY -
Укажите, как пользователь итератора будет читать или записывать в
op[i]. Точно один из этих флагов должен быть указан для каждого операнда.
-
NPY_ITER_COPY -
Разрешить копирование
op[i], если он не соответствует требованиям к типу данных или выравниванию, как указано в флагах и параметрах конструктора.
-
-
NPY_ITER_UPDATEIFCOPY -
Вызывает
NPY_ITER_COPY, и когда операнд массива помечен для записи и копируется, вызывает копирование данных из копии обратно вop[i]при уничтожении итератора.Если операнд помечен как только для записи, а копия необходима, будет создан неинициализированный временный массив, а затем скопирован обратно в
op[i]при уничтожении, вместо выполнения ненужной операции копирования.
-
NPY_ITER_NBO
-
NPY_ITER_ALIGNED
-
NPY_ITER_CONTIG -
Принуждает итератор предоставлять данные для
op[i]в родном порядке байтов, выровненные согласно требованиям dtype, непрерывные или любая комбинация.По умолчанию итератор генерирует указатели в предоставленные массивы, которые могут быть выровнены или не выровнены, а также иметь любой порядок байтов. Если копирование или буферизация не включены, а данные операнда не удовлетворяют ограничениям, будет поднято исключение.
Ограничение непрерывности относится только к внутреннему циклу; последующие внутренние циклы могут иметь произвольные изменения указателей.
Если запрашиваемый тип данных находится в неродном порядке байтов, флаг NBO переопределяет его, и запрашиваемый тип данных преобразуется в родной порядок байтов.
-
NPY_ITER_ALLOCATE -
Используется для выходных массивов и требует, чтобы был установлен флаг
NPY_ITER_WRITEONLYилиNPY_ITER_READWRITE. Еслиop[i]равен NULL, создается новый массив с окончательными размерами векторизации и структурой, соответствующей порядку итерации итератора.Когда
op[i]равен NULL, запрашиваемый тип данныхop_dtypes[i]также может быть NULL, в этом случае он автоматически генерируется из типов данных массивов, помеченных как читаемые. Правила генерации типа данных такие же, как и для UFuncs. Следует обратить особое внимание на обработку порядка байтов в выбранном dtype. Если имеется ровно один входной параметр, тип данных входного параметра используется как есть. В противном случае, если несколько входных типов данных объединены вместе, вывод будет в родном порядке байтов.После выделения с этим флагом вызывающая сторона может получить новый массив, вызвав
NpyIter_GetOperandArrayи получив i-й объект в возвращённом массиве C. Вызывающая сторона должна вызвать Py_INCREF для него, чтобы получить ссылку на массив.
-
NPY_ITER_NO_SUBTYPE -
Для использования с
NPY_ITER_ALLOCATE, этот флаг отключает выделение подтипа массива для вывода, принуждая его к прямому ndarray.TODO: Возможно, было бы лучше ввести функцию
NpyIter_GetWrappedOutputи удалить этот флаг?
-
NPY_ITER_NO_BROADCAST -
Обеспечивает, что входной или выходной массив точно соответствует размерам итерации.
-
NPY_ITER_ARRAYMASK -
Добавлена в версии 1.7.
Указывает, что этот операнд является маской, используемой для выбора элементов при записи в операнды, которым применён флаг
NPY_ITER_WRITEMASKED. Только один операнд может иметь применённый флагNPY_ITER_ARRAYMASK.Тип данных операнда с этим флагом должен быть либо
NPY_BOOL,NPY_MASK, или структурированным типом данных, поля которого являются допустимыми типами данных маски. В последнем случае он должен совпадать со структурированным операндом, помеченным как WRITEMASKED, так как он определяет маску для каждого поля этого массива.Этот флаг влияет только на запись из буфера обратно в массив. Это означает, что если операнд также имеет флаг
NPY_ITER_READWRITEилиNPY_ITER_WRITEONLY, код, выполняющий итерацию, может записывать в этот операнд, чтобы контролировать, какие элементы будут нетронутыми, а какие изменены. Это полезно, когда маска должна быть комбинацией входных масок, например. Значения масок можно создавать с помощью функцииNpyMask_Create.
-
NPY_ITER_WRITEMASKED -
Добавлена в версии 1.7.
Указывает, что только элементы, которые операнд с флагом ARRAYMASK указывает, должны быть изменены во время итерации. В общем случае итератор этого не проверяет, за этим следит код, выполняющий итерацию. Код может использовать встроенную функцию
NpyMask_IsExposedдля проверки того, разрешает ли маска на определённом элементе запись.Когда этот флаг используется, а этот операнд буферизован, это меняет способ копирования данных из буфера в массив. Используется процедура копирования с маской, которая копирует только элементы в буфере, для которых функция
NpyMask_IsExposedвозвращает true для соответствующего элемента в операнде ARRAYMASK.
-
-
NpyIter* NpyIter_AdvancedNew(npy_intp nop, PyArrayObject** op, npy_uint32 flags, NPY_ORDER order, NPY_CASTING casting, npy_uint32* op_flags, PyArray_Descr** op_dtypes, int oa_ndim, int** op_axes, npy_intp* itershape, npy_intp buffersize) -
Расширяет
NpyIter_MultiNewнесколькими расширенными параметрами, обеспечивающими больший контроль над векторизацией и буферизацией.Если значения -1/NULL передаются в
oa_ndim,op_axes,itershape, иbuffersize, это эквивалентноNpyIter_MultiNew.Параметр
oa_ndim, когда не равен нулю или -1, указывает количество измерений, которые будут проитерированы с настраиваемой векторизацией. Если он задан,op_axesдолжен, иitershapeможет быть задан. Параметрop_axesпозволяет подробно управлять тем, как оси массивов операндов сопоставляются друг с другом и проитерированы. Вop_axes, вы должны предоставить массив указателей на массивы размеромnopтипаnpy_intp. Если запись вop_axesравна NULL, будут применены стандартные правила векторизации. Вop_axes[j][i]хранится либо допустимая ось массиваop[j], либо -1, что означаетnewaxis. Внутри каждого массиваop_axes[j]оси не могут повторяться. Вот пример того, как стандартная векторизация применяется к 3D-массиву, 2D-массиву, 1D-массиву и скаляру.Примечание: До NumPy 1.8
oa_ndim == 0` was used for signalling that that ``op_axesиitershapeне используются. Это устарело и должно быть заменено на -1. Для достижения лучшей обратной совместимости в этом случае следует использоватьNpyIter_MultiNew.int oa_ndim = 3; /* # iteration axes */ int op0_axes[] = {0, 1, 2}; /* 3-D operand */ int op1_axes[] = {-1, 0, 1}; /* 2-D operand */ int op2_axes[] = {-1, -1, 0}; /* 1-D operand */ int op3_axes[] = {-1, -1, -1} /* 0-D (scalar) operand */ int* op_axes[] = {op0_axes, op1_axes, op2_axes, op3_axes};Параметр
itershapeпозволяет принудительно задать определённую форму итерации итератора. Это массив длинойoa_ndim. Когда запись отрицательна, её значение определяется из операндов. Этот параметр позволяет автоматически выделять выходные массивы с дополнительными измерениями, которые не соответствуют ни одному измерению входа.Если
buffersizeравно нулю, используется размер буфера по умолчанию, в противном случае он указывает размер буфера.Возвращает NULL в случае ошибки, в противном случае возвращает выделенный итератор.
-
NpyIter* NpyIter_Copy(NpyIter* iter) -
Создаёт копию данного итератора. Эта функция главным образом предназначена для поддержки многопотоковой итерации данных.
TODO: Переместить в раздел о многопотоковой итерации.
Рекомендуемый подход к многопотоковой итерации — сначала создать итератор с флагами
NPY_ITER_EXTERNAL_LOOP,NPY_ITER_RANGED,NPY_ITER_BUFFERED,NPY_ITER_DELAY_BUFALLOCи возможноNPY_ITER_GROWINNER. Создайте копию этого итератора для каждого потока (кроме одного для первого итератора). Затем возьмите диапазон индексов итерации[0, NpyIter_GetIterSize(iter))и разделите его на задачи, например, используя цикл TBB parallel_for. Когда поток получает задачу для выполнения, он использует свою копию итератора, вызываяNpyIter_ResetToIterIndexRangeи итерируя по всему диапазону.При использовании итератора в многопоточном коде или в коде, не удерживающем интерпретатор Python, необходимо принять меры, чтобы вызывать только безопасные функции в этом контексте.
NpyIter_Copyнельзя безопасно вызывать без блокировки интерпретатора Python, потому что она увеличивает количество ссылок на Python. ФункцииReset*и некоторые другие функции могут быть безопасно вызваны, передав параметрerrmsgкак не NULL, чтобы функции передавали ошибки через него вместо установки исключения Python.
-
int NpyIter_RemoveAxis(NpyIter* iter, int axis)`` -
Удаляет ось из итерации. Это требует, чтобы для создания итератора был установлен флаг
NPY_ITER_MULTI_INDEX, и не работает, если буферизация включена или отслеживается индекс. Эта функция также сбрасывает итератор в исходное состояние.Это полезно для настройки цикла накопления, например. Итератор сначала можно создать со всеми измерениями, включая ось накопления, чтобы выходной массив создавался правильно. Затем можно удалить ось накопления и выполнить вычисление вложенным образом.
ПРЕДУПРЕЖДЕНИЕ: Эта функция может изменить внутреннюю структуру памяти итератора. Все кэшированные функции или указатели итератора должны быть получены заново! Диапазон итератора также будет сброшен.
Возвращает
NPY_SUCCEEDилиNPY_FAIL.
-
int NpyIter_RemoveMultiIndex(NpyIter* iter) -
Если итератор отслеживает многоиндекс, эта функция удаляет поддержку многоиндексов и выполняет дальнейшую оптимизацию итератора, возможную, если многоиндексы не нужны. Эта функция также сбрасывает итератор в его начальное состояние.
ПРЕДУПРЕЖДЕНИЕ: Эта функция может изменить внутреннюю структуру памяти итератора. Все кэшированные функции или указатели итератора должны быть получены повторно!
После вызова этой функции
NpyIter_HasMultiIndex(iter)вернёт false.Возвращает
NPY_SUCCEEDилиNPY_FAIL.
-
int NpyIter_EnableExternalLoop(NpyIter* iter) -
Если была вызвана
NpyIter_RemoveMultiIndex, возможно, вам нужно включить флагNPY_ITER_EXTERNAL_LOOP. Этот флаг не разрешается использовать вместе сNPY_ITER_MULTI_INDEX, поэтому эта функция предназначена для включения этой функции после вызоваNpyIter_RemoveMultiIndex. Эта функция также сбрасывает итератор в его начальное состояние.ПРЕДУПРЕЖДЕНИЕ: Эта функция изменяет внутреннюю логику итератора. Все кэшированные функции или указатели итератора должны быть получены повторно!
Возвращает
NPY_SUCCEEDилиNPY_FAIL.
-
int NpyIter_Deallocate(NpyIter* iter) -
Освобождает объект итератора. Это дополнительно освобождает все сделанные копии, вызывая поведение UPDATEIFCOPY при необходимости.
Возвращает
NPY_SUCCEEDилиNPY_FAIL.
-
int NpyIter_Reset(NpyIter* iter, char** errmsg) -
Сбрасывает итератор в его начальное состояние, в начало диапазона итерации.
Возвращает
NPY_SUCCEEDилиNPY_FAIL. Если errmsg не NULL, исключение Python не устанавливается, когда возвращаетсяNPY_FAIL. Вместо этого *errmsg устанавливается в сообщение об ошибке. При не-NULL errmsg, функцию можно безопасно вызывать без блокировки интерпретатора Python.
-
int NpyIter_ResetToIterIndexRange(NpyIter* iter, npy_intp istart, npy_intp iend, char** errmsg) -
Сбрасывает итератор и ограничивает его диапазоном
iterindex[istart, iend). См.NpyIter_Copyдля объяснения, как использовать это для многопоточной итерации. Для этого требуется, чтобы флагNPY_ITER_RANGEDбыл передан конструктору итератора.Если вы хотите сбросить и диапазон
iterindexи базовые указатели одновременно, можно сделать следующее, чтобы избежать дополнительной копии буфера (не забудьте добавить проверки кода возврата при копировании этого кода)./* Set to a trivial empty range */ NpyIter_ResetToIterIndexRange(iter, 0, 0); /* Set the base pointers */ NpyIter_ResetBasePointers(iter, baseptrs); /* Set to the desired range */ NpyIter_ResetToIterIndexRange(iter, istart, iend);
Возвращает
NPY_SUCCEEDилиNPY_FAIL. Если errmsg не NULL, исключение Python не устанавливается, когда возвращаетсяNPY_FAIL. Вместо этого *errmsg устанавливается в сообщение об ошибке. При не-NULL errmsg, функцию можно безопасно вызывать без блокировки интерпретатора Python.
-
int NpyIter_ResetBasePointers(NpyIter *iter, char** baseptrs, char** errmsg) -
Сбрасывает итератор в его начальное состояние, но используя значения в
baseptrsдля данных вместо указателей из массивов, которые итерируются. Эта функция предназначена для использования вместе с параметромop_axes, кодом вложенной итерации с двумя или более итераторами.Возвращает
NPY_SUCCEEDилиNPY_FAIL. Если errmsg не NULL, исключение Python не устанавливается, когда возвращаетсяNPY_FAIL. Вместо этого *errmsg устанавливается в сообщение об ошибке. При не-NULL errmsg, функцию можно безопасно вызывать без блокировки интерпретатора Python.TODO: Переместить следующее в специальный раздел по вложенным итераторам.
Создание итераторов для вложенной итерации требует некоторой осторожности. Все операнды итератора должны точно совпадать, или вызовы
NpyIter_ResetBasePointersбудут недействительными. Это означает, что автоматические копии и выделение вывода не следует использовать бездумно. Можно по-прежнему использовать функции автоматического преобразования и приведения типов итератора, создав один из итераторов со всеми параметрами преобразования, затем получить выделенные операнды с помощью функцииNpyIter_GetOperandArrayи передать их в конструкторы для остальных итераторов.ПРЕДУПРЕЖДЕНИЕ: При создании итераторов для вложенной итерации код не должен использовать одну и ту же размерность более одного раза в разных итераторах. Если это сделано, вложенная итерация произведёт указатели за пределами границ во время итерации.
ПРЕДУПРЕЖДЕНИЕ: При создании итераторов для вложенной итерации буферизация может применяться только к самому внутреннему итератору. Если буферизованный итератор используется в качестве источника для
baseptrs, он будет указывать на небольшой буфер, а не на массив, и внутренняя итерация будет недействительной.Шаблон использования вложенных итераторов следующий.
NpyIter *iter1, *iter1; NpyIter_IterNextFunc *iternext1, *iternext2; char **dataptrs1; /* * With the exact same operands, no copies allowed, and * no axis in op_axes used both in iter1 and iter2. * Buffering may be enabled for iter2, but not for iter1. */ iter1 = ...; iter2 = ...; iternext1 = NpyIter_GetIterNext(iter1); iternext2 = NpyIter_GetIterNext(iter2); dataptrs1 = NpyIter_GetDataPtrArray(iter1); do { NpyIter_ResetBasePointers(iter2, dataptrs1); do { /* Use the iter2 values */ } while (iternext2(iter2)); } while (iternext1(iter1));
-
int NpyIter_GotoMultiIndex(NpyIter* iter, npy_intp* multi_index) -
Настраивает итератор для указания на указанные индексы многоиндекса, указанные в
multi_index. Возвращает ошибку, если многоиндекс не отслеживается, индексы находятся за пределами границ или итерация внутреннего цикла отключена.Возвращает
NPY_SUCCEEDилиNPY_FAIL.
-
int NpyIter_GotoIndex(NpyIter* iter, npy_intp index) -
Настраивает итератор на указанный
index. Если итератор был создан с флагомNPY_ITER_C_INDEX,index— индекс в порядке C, а если итератор был создан с флагомNPY_ITER_F_INDEX,index— индекс в порядке Fortran. Возвращает ошибку, если не отслеживается индекс, индекс находится за пределами границ или итерация внутреннего цикла отключена.Возвращает
NPY_SUCCEEDилиNPY_FAIL.
-
npy_intp NpyIter_GetIterSize(NpyIter* iter) -
Возвращает количество итерируемых элементов. Это произведение всех измерений в форме. Когда отслеживается многоиндекс (и может быть вызван
NpyIter_RemoveAxis), размер может быть-1, чтобы указать, что итератор слишком большой. Такой итератор недействителен, но может стать действительным после вызоваNpyIter_RemoveAxis. Нет необходимости проверять этот случай.
-
npy_intp NpyIter_GetIterIndex(NpyIter* iter) -
Получает
iterindexитератора, который является индексом, соответствующим порядку итерации итератора.
-
void NpyIter_GetIterIndexRange(NpyIter* iter, npy_intp* istart, npy_intp* iend) -
Получает
iterindexподдиапазон, который итерируется. ЕслиNPY_ITER_RANGEDне был указан, это всегда возвращает диапазон[0, NpyIter_IterSize(iter)).
-
int NpyIter_GotoIterIndex(NpyIter* iter, npy_intp iterindex) -
Настраивает итератор на указанный
iterindex. IterIndex — индекс, соответствующий порядку итерации итератора. Возвращает ошибку, еслиiterindexнаходится за пределами границ, буферизация включена или итерация внутреннего цикла отключена.Возвращает
NPY_SUCCEEDилиNPY_FAIL.
-
npy_bool NpyIter_HasDelayedBufAlloc(NpyIter* iter) -
Возвращает 1, если флаг
NPY_ITER_DELAY_BUFALLOCбыл передан конструктору итератора, и ни один из функций Reset ещё не был вызван, иначе 0.
-
npy_bool NpyIter_HasExternalLoop(NpyIter* iter) -
Возвращает 1, если вызывающему элементу необходимо обработать самый внутренний одномерный цикл, или 0, если итератор обрабатывает все циклы. Это контролируется флагом конструктора
NPY_ITER_EXTERNAL_LOOPилиNpyIter_EnableExternalLoop.
-
npy_bool NpyIter_HasMultiIndex(NpyIter* iter) -
Возвращает 1, если итератор был создан с флагом
NPY_ITER_MULTI_INDEX, иначе 0.
-
npy_bool NpyIter_HasIndex(NpyIter* iter) -
Возвращает 1, если итератор был создан с флагом
NPY_ITER_C_INDEXилиNPY_ITER_F_INDEX, иначе 0.
-
npy_bool NpyIter_RequiresBuffering(NpyIter* iter) -
Возвращает 1, если итератор требует буферизации, что происходит, когда операнду требуется преобразование или выравнивание, и поэтому он не может быть использован напрямую.
-
npy_bool NpyIter_IsBuffered(NpyIter* iter) -
Возвращает 1, если итератор был создан с флагом
NPY_ITER_BUFFERED, иначе 0.
-
npy_bool NpyIter_IsGrowInner(NpyIter* iter) -
Возвращает 1, если итератор был создан с флагом
NPY_ITER_GROWINNER, иначе 0.
-
npy_intp NpyIter_GetBufferSize(NpyIter* iter) -
Если итератор буферизован, возвращает размер используемого буфера, иначе возвращает 0.
-
int NpyIter_GetNDim(NpyIter* iter) -
Возвращает количество измерений, которые итерируются. Если многоиндекс не запрашивался в конструкторе итератора, это значение может быть меньше, чем количество измерений в исходных объектах.
-
int NpyIter_GetNOp(NpyIter* iter) -
Возвращает количество операндов в итераторе.
Когда
NPY_ITER_USE_MASKNAиспользуется с операндом, новый операнд добавляется в конец списка операндов в итераторе для отслеживания маски NA этого операнда. Таким образом, это равно числу операндов построения плюс числу операндов, для которых был указан флагNPY_ITER_USE_MASKNA.
-
int NpyIter_GetFirstMaskNAOp(NpyIter* iter) -
Новое в версии 1.7.
Возвращает индекс первого операнда маски NA в массиве. Это значение равно числу операндов, переданных в конструктор.
-
npy_intp* NpyIter_GetAxisStrideArray(NpyIter* iter, int axis) -
Получает массив шагов для указанной оси. Требуется, чтобы итератор отслеживал многоиндекс, и чтобы буферизация не была включена.
Это может быть использовано, когда вы хотите каким-то образом согласовать оси операндов, а затем удалить их с помощью
NpyIter_RemoveAxisдля обработки их вручную. Вызвав эту функцию до удаления осей, вы можете получить шаги для ручной обработки.Возвращает
NULLпри ошибке.
-
int NpyIter_GetShape(NpyIter* iter, npy_intp* outshape) -
Возвращает транслированную форму итератора в
outshape. Это можно вызвать только для итератора, отслеживающего многоиндекс.Возвращает
NPY_SUCCEEDилиNPY_FAIL.
-
PyArray_Descr** NpyIter_GetDescrArray(NpyIter* iter) -
Это возвращает указатель на
nopтипы данных Descrs для объектов, по которым ведется итерация. Результат указывает наiter, поэтому вызывающий код не получает никаких ссылок на Descrs.Этот указатель можно кэшировать перед циклом итерации, вызов
iternextне изменит его.
-
PyObject** NpyIter_GetOperandArray(NpyIter* iter) -
Это возвращает указатель на
nopоперанды PyObjects, по которым ведется итерация. Результат указывает наiter, поэтому вызывающий код не получает никаких ссылок на PyObjects.
-
npy_int8* NpyIter_GetMaskNAIndexArray(NpyIter* iter) -
Новое в версии 1.7.
Это возвращает указатель на
nopиндексы, которые сопоставляют операнды построения сNPY_ITER_USE_MASKNAпомеченными их соответствующими операндами маски NA и наоборот. Для операндов, которые не были помеченыNPY_ITER_USE_MASKNA, в этом массиве содержатся отрицательные значения.
-
PyObject* NpyIter_GetIterView(NpyIter* iter, npy_intp i) -
Это возвращает ссылку на новый вид ndarray, который представляет собой вид на i-й объект в массиве
NpyIter_GetOperandArray, чьи размеры и шаги соответствуют внутренней оптимизированной схеме итерации. Итерация в порядке C этого вида эквивалентна порядку итерации итератора.Например, если итератор был создан с одним массивом в качестве входных данных, и было возможно переупорядочить все его оси, а затем свернуть их в одну строчную итерацию, это вернет представление, которое является одномерным массивом.
-
void NpyIter_GetReadFlags(NpyIter* iter, char* outreadflags) -
Заполняет
nopфлаги. Устанавливаетoutreadflags[i]в 1, если изop[i]можно читать, и в 0, если нет.
-
void NpyIter_GetWriteFlags(NpyIter* iter, char* outwriteflags) -
Заполняет
nopфлаги. Устанавливаетoutwriteflags[i]в 1, если изop[i]можно записывать, и в 0, если нет.
-
int NpyIter_CreateCompatibleStrides(NpyIter* iter, npy_intp itemsize, npy_intp* outstrides) -
Создает набор шагов, которые совпадают с шагами выходного массива, созданного с флагом
NPY_ITER_ALLOCATE, где для op_axes был передан NULL. Это для данных, упакованных непрерывно, но необязательно в порядке C или Fortran. Это следует использовать вместе сNpyIter_GetShapeиNpyIter_GetNDimс флагомNPY_ITER_MULTI_INDEX, переданным в конструктор.Случай использования этой функции – это согласовать форму и расположение итератора и добавить одну или несколько измерений. Например, для генерации вектора на значение входных данных для числового градиента вы передаете ndim*itemsize для itemsize, затем добавляете еще одно измерение в конец размером ndim и шагом itemsize. Для матрицы Гессе вы делаете то же самое, но добавляете два измерения или используете симметрию и упаковываете ее в 1 измерение с определенным кодированием.
Эта функция может быть вызвана только в том случае, если итератор отслеживает многоиндекс и если
NPY_ITER_DONT_NEGATE_STRIDESиспользовался для предотвращения итерации оси в обратном порядке.Если массив создается с помощью этого метода, простое добавление ‘itemsize’ для каждой итерации будет перемещаться по новому массиву, соответствующему итератору.
Возвращает
NPY_SUCCEEDилиNPY_FAIL.
-
npy_bool NpyIter_IsFirstVisit(NpyIter* iter, int iop) -
Новое в версии 1.7.
Проверяет, является ли это первый раз, когда элементы указанного операнда сокращения, на которые указывает итератор, видятся впервые. Функция возвращает разумный ответ для операндов сокращения и когда буферизация отключена. Ответ может быть неверным для буферизованных операндов, не являющихся операндами сокращения.
Эта функция предназначена для использования только в режиме EXTERNAL_LOOP и даст неправильные ответы, когда этот режим не включен.
Если эта функция возвращает true, вызывающий код также должен проверить шаг внутреннего цикла операнда, потому что если этот шаг равен 0, то только первый элемент самого внутреннего внешнего цикла посещается впервые.
ПРЕДУПРЕЖДЕНИЕ: по причинам производительности ‘iop’ не проверяется на границы, не гарантируется, что ‘iop’ на самом деле является операндом сокращения, и не гарантируется, что режим EXTERNAL_LOOP включен. Эти проверки являются обязанностью вызывающего кода и должны выполняться вне любых внутренних циклов.
Функции для итерации
-
NpyIter_IterNextFunc* NpyIter_GetIterNext(NpyIter* iter, char** errmsg) -
Возвращает указатель на функцию для итерации. Специализированная версия указателя на функцию может быть вычислена этой функцией вместо того, чтобы храниться в структуре итератора. Таким образом, для получения хорошей производительности требуется, чтобы указатель на функцию был сохранен в переменной, а не извлекался для каждой итерации цикла.
Возвращает NULL, если произошла ошибка. Если errmsg не равен NULL, исключение Python не устанавливается, когда
NPY_FAILвозвращается. Вместо этого *errmsg устанавливается в сообщение об ошибке. Когда errmsg не равен NULL, функция может быть безопасно вызвана без удержания Python GIL.Типичная конструкция цикла выглядит следующим образом.
NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL); char** dataptr = NpyIter_GetDataPtrArray(iter); do { /* use the addresses dataptr[0], ... dataptr[nop-1] */ } while(iternext(iter));Когда
NPY_ITER_EXTERNAL_LOOPуказан, типичная конструкция внутреннего цикла выглядит следующим образом.NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL); char** dataptr = NpyIter_GetDataPtrArray(iter); npy_intp* stride = NpyIter_GetInnerStrideArray(iter); npy_intp* size_ptr = NpyIter_GetInnerLoopSizePtr(iter), size; npy_intp iop, nop = NpyIter_GetNOp(iter); do { size = *size_ptr; while (size--) { /* use the addresses dataptr[0], ... dataptr[nop-1] */ for (iop = 0; iop < nop; ++iop) { dataptr[iop] += stride[iop]; } } } while (iternext());Обратите внимание, что мы используем массив dataptr внутри итератора, а не копируем значения в локальную временную переменную. Это возможно, потому что когда
iternext()вызывается, эти указатели будут перезаписаны свежими значениями, а не инкрементально обновлены.Если используется буфер фиксированного размера во время компиляции (оба флага
NPY_ITER_BUFFEREDиNPY_ITER_EXTERNAL_LOOP), внутренний размер также может использоваться в качестве сигнала. Размер гарантируется, что станет нулевым, когдаiternext()возвращает false, что позволяет использовать следующую конструкцию цикла. Обратите внимание, что если вы используете эту конструкцию, вы не должны передаватьNPY_ITER_GROWINNERв качестве флага, потому что это приведет к увеличению размеров в некоторых случаях./* The constructor should have buffersize passed as this value */ #define FIXED_BUFFER_SIZE 1024 NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL); char **dataptr = NpyIter_GetDataPtrArray(iter); npy_intp *stride = NpyIter_GetInnerStrideArray(iter); npy_intp *size_ptr = NpyIter_GetInnerLoopSizePtr(iter), size; npy_intp i, iop, nop = NpyIter_GetNOp(iter); /* One loop with a fixed inner size */ size = *size_ptr; while (size == FIXED_BUFFER_SIZE) { /* * This loop could be manually unrolled by a factor * which divides into FIXED_BUFFER_SIZE */ for (i = 0; i < FIXED_BUFFER_SIZE; ++i) { /* use the addresses dataptr[0], ... dataptr[nop-1] */ for (iop = 0; iop < nop; ++iop) { dataptr[iop] += stride[iop]; } } iternext(); size = *size_ptr; } /* Finish-up loop with variable inner size */ if (size > 0) do { size = *size_ptr; while (size--) { /* use the addresses dataptr[0], ... dataptr[nop-1] */ for (iop = 0; iop < nop; ++iop) { dataptr[iop] += stride[iop]; } } } while (iternext());
-
NpyIter_GetMultiIndexFunc *NpyIter_GetGetMultiIndex(NpyIter* iter, char** errmsg) -
Возвращает указатель на функцию для получения текущего многоиндекса итератора. Возвращает NULL, если итератор не отслеживает многоиндекс. Рекомендуется кэшировать этот указатель на функцию в локальной переменной перед циклом итерации.
Возвращает NULL, если произошла ошибка. Если errmsg не равен NULL, исключение Python не устанавливается, когда
NPY_FAILвозвращается. Вместо этого *errmsg устанавливается в сообщение об ошибке. Когда errmsg не равен NULL, функция может быть безопасно вызвана без удержания Python GIL.
-
char** NpyIter_GetDataPtrArray(NpyIter* iter) -
Это возвращает указатель на
nopуказатели на данные. ЕслиNPY_ITER_EXTERNAL_LOOPне был указан, каждый указатель на данные указывает на текущий элемент данных итератора. Если не был указан внутренний цикл итерации, он указывает на первый элемент данных внутреннего цикла.Этот указатель можно кэшировать перед циклом итерации, вызов
iternextне изменит его. Эта функция может быть безопасно вызвана без удержания Python GIL.
-
char** NpyIter_GetInitialDataPtrArray(NpyIter* iter) -
Получает массив указателей на данные непосредственно в массивы (никогда не в буферы), соответствующие индексу итерации 0.
Эти указатели отличаются от указателей, принятых
NpyIter_ResetBasePointers, потому что направление по некоторым осям могло быть перевернуто.Эта функция может быть безопасно вызвана без удержания Python GIL.
-
npy_intp* NpyIter_GetIndexPtr(NpyIter* iter) -
Это возвращает указатель на отслеживаемый индекс или NULL, если индекс не отслеживается. Он используется только в том случае, если один из флагов
NPY_ITER_C_INDEXилиNPY_ITER_F_INDEXбыл указан во время построения.
Когда используется флаг NPY_ITER_EXTERNAL_LOOP, код должен знать параметры для выполнения внутреннего цикла. Эти функции предоставляют эту информацию.
-
npy_intp* NpyIter_GetInnerStrideArray(NpyIter* iter) -
Возвращает указатель на массив
nopшагов, по одному на каждый итерируемый объект, для использования внутренним циклом.Этот указатель можно кэшировать перед циклом итерации, вызов
iternextне изменит его. Эта функция может быть безопасно вызвана без удержания Python GIL.ПРЕДУПРЕЖДЕНИЕ: хотя указатель можно кэшировать, его значения могут измениться, если итератор буферизован.
-
npy_intp* NpyIter_GetInnerLoopSizePtr(NpyIter* iter) -
Возвращает указатель на количество итераций, которое должно выполнить внутреннее цикл.
Этот адрес может быть кэширован перед циклом итерации, вызов
iternextне изменит его. Само значение может изменяться во время итерации, особенно если включена буферизация. Данную функцию можно безопасно вызывать без удержания Python GIL.
-
void NpyIter_GetInnerFixedStrideArray(NpyIter* iter, npy_intp* out_strides) -
Получает массив шагов, которые являются фиксированными или не изменятся в течение всей итерации. Для шагов, которые могут измениться, значение NPY_MAX_INTP помещается в шаг.
После подготовки итератора к итерации (после сброса, если был использован
NPY_DELAY_BUFALLOC), вызовите эту функцию, чтобы получить шаги, которые могут быть использованы для выбора быстрой внутренней функции цикла. Например, если шаг равен 0, это означает, что внутренний цикл всегда может загрузить своё значение в переменную один раз, а затем использовать эту переменную на протяжении всего цикла, или если шаг равен размеру элемента, может быть использована непрерывная версия для этого операнда.Эту функцию можно безопасно вызывать без удержания Python GIL.
Преобразование из предыдущих итераторов NumPy
Старый API итераторов включает функции, такие как PyArrayIter_Check, PyArray_Iter* и PyArray_ITER_*. Массив многократных итераторов включает PyArray_MultiIter*, PyArray_Broadcast и PyArray_RemoveSmallest. Новый дизайн итератора заменяет всю эту функциональность одним объектом и соответствующим API. Одна из целей нового API заключается в том, чтобы все используемые существующие итераторы можно было заменить новым итератором без особых усилий. В версии 1.6 основным исключением из этого правила является итератор окрестностей, у которого нет соответствующих функций в этом итераторе.
Ниже приведена таблица преобразования функций, которые следует использовать с новым итератором:
© 2008–2016 NumPy Developers
Licensed under the NumPy License.
https://docs.scipy.org/doc/numpy-1.11.0/reference/c-api.iterator.html