Интерфейс итератора массива
Новая версия с 1.6.
Итератор массива
Итератор массива инкапсулирует многие ключевые функции ufuncs, позволяя коду пользователя поддерживать такие функции, как параметры вывода, сохранение расположения памяти и буферизацию данных с несоответствующим выравниванием или типом, без необходимости сложных кодировок.
На этой странице документируется API итератора. Итератор называется NpyIter, а функции называются NpyIter_*.
Существует вводное руководство по итерации массивов, которое может быть полезно для тех, кто использует этот C API. Во многих случаях, тестирование идей путём создания итератора в 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)имеет значение true, в этом случае он может не освобождать 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, в этом случае он автоматически генерируется из типов данных массивов, помеченных как читаемые. Правила генерации dtype такие же, как и для UFunc. Важно отметить обработку порядка байтов в выбранном dtype. Если есть ровно один вход, используется dtype входа без изменений. В противном случае, если несколько входных dtypes объединяются вместе, выходной будет в родном порядке байтов.После выделения с этим флагом вызывающий код может получить новый массив, вызвав
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на массивы размеромoa_ndimтипаnpy_intp. Если запись вop_axesравна NULL, будут применяться обычные правила вещания. Вop_axes[j][i]хранится либо допустимая осьop[j], либо -1, что означаетnewaxis. Внутри каждого массиваop_axes[j]оси не могут повторяться. Следующий пример показывает, как обычное вещание применяется к 3-мерному массиву, 2-мерному массиву, 1-мерному массиву и скаляру.Примечание: Перед 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 устанавливается в сообщение об ошибке. Когда errmsg не NULL, функцию можно безопасно вызвать без блокировки Python GIL.
-
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 устанавливается в сообщение об ошибке. Когда errmsg не NULL, функцию можно безопасно вызвать без блокировки Python GIL.
-
int NpyIter_ResetBasePointers(NpyIter *iter, char** baseptrs, char** errmsg) -
Сбрасывает итератор в исходное состояние, но использует значения в
baseptrsдля данных вместо указателей из массивов, по которым выполняется итерация. Эта функция предназначена для использования вместе с параметромop_axesкодом вложенной итерации с двумя или более итераторами.Возвращает
NPY_SUCCEEDилиNPY_FAIL. Если errmsg не NULL, исключение Python не устанавливается, когда возвращаетсяNPY_FAIL. Вместо этого *errmsg устанавливается в сообщение об ошибке. Когда errmsg не NULL, функцию можно безопасно вызвать без блокировки Python GIL.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) -
Настраивает итератор для указания на
ndimиндексы, на которые указывает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, если вызывающему объекту нужно обработать вложенный 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объектов, по которым выполняется итерация. Результат указывает вiter, поэтому вызывающий код не получает никаких ссылок на описатели.Этот указатель можно кэшировать перед циклом итерации, вызов
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. Для матрицы Гессе вы делаете то же самое, но добавляете два измерения или используете симметрию и упаковываете её в одно измерение с определённым кодированием.
Эта функция может быть вызвана только если итератор отслеживает многоиндекс и если
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 устанавливается в сообщение об ошибке. При не-NULL errmsg функция может быть безопасно вызвана без удержания 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 устанавливается в сообщение об ошибке. При не-NULL errmsg функция может быть безопасно вызвана без удержания GIL.
-
char** NpyIter_GetDataPtrArray(NpyIter* iter) -
Возвращает указатель на
nopуказатели данных. ЕслиNPY_ITER_EXTERNAL_LOOPне был указан, каждый указатель данных указывает на текущий элемент данных итератора. Если не был указан внутренний цикл итерации, он указывает на первый элемент данных внутреннего цикла.Этот указатель можно кэшировать перед циклом итерации, вызов
iternextне изменит его. Функция может быть безопасно вызвана без удержания GIL.
-
char** NpyIter_GetInitialDataPtrArray(NpyIter* iter) -
Получает массив указателей данных непосредственно в массивы (никогда в буферы), соответствующие индексу итерации 0.
Эти указатели отличаются от указателей, принятых
NpyIter_ResetBasePointers, потому что направление по некоторым осям может быть обратным.Функция может быть безопасно вызвана без удержания 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.10.1/reference/c-api.iterator.html