Array Iterator API

原文:https://docs.scipy.org/doc/numpy/reference/c-api.iterator.html

译者:飞龙 UsyiyiCN

校对:(虚位以待)

版本1.6中的新功能。

Array Iterator

数组迭代器封装了ufuncs中的许多关键特性,允许用户代码支持诸如输出参数,保留存储器布局以及缓冲具有错误对齐或类型的数据的功能,而不需要困难的编码。

此页面记录了迭代器的API。迭代器命名为NpyIter,函数名为NpyIter_*

有一个introductory guide to array iteration,对那些使用此C API的用户可能会感兴趣。在许多情况下,在编写C迭代代码之前,通过在Python中创建迭代器来测试想法是一个好主意。

Simple Iteration Example

熟悉迭代器的最好方法是查看它在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;
}

Simple Multi-Iteration Example

这里是一个使用迭代器的简单复制函数。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;
}

Iterator Data Types

迭代器布局是一个内部细节,用户代码只看到一个不完整的结构。

NpyIter

这是一个不透明的指针类型的迭代器。访问其内容只能通过迭代器API完成。

NpyIter_Type

这是将迭代器暴露给Python的类型。目前,没有公开提供访问Python创建的迭代器的值的API。如果在Python中创建一个迭代器,它必须在Python中使用,反之亦然。这样的API很可能在未来的版本中创建。

NpyIter_IterNextFunc

这是由NpyIter_GetIterNext返回的迭代循环的函数指针。

NpyIter_GetMultiIndexFunc

这是一个函数指针,用于获取当前迭代器多索引,由NpyIter_GetGetMultiIndex返回。

Construction and Destruction

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_CASTINGNPY_EQUIV_CASTINGNPY_SAFE_CASTINGNPY_SAME_KIND_CASTINGNPY_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)

使用常规NumPy广播规则创建用于广播op中提供的nop数组对象的迭代器。

任何NPY_ORDER枚举值都可以传递到order对于有效的迭代,NPY_KEEPORDER是最好的选项,其他顺序强制执行特定的迭代模式。当使用NPY_KEEPORDER时,如果您还想确保沿轴不反转,您应该传递标志NPY_ITER_DONT_NEGATE_STRIDES

任何NPY_CASTING枚举值都可以传递到casting值包括NPY_NO_CASTINGNPY_EQUIV_CASTINGNPY_SAFE_CASTINGNPY_SAME_KIND_CASTINGNPY_UNSAFE_CASTING要允许强制转换,还必须启用复制或缓冲。

如果op_dtypes不是NULL,则它为每个op[i]指定数据类型或NULL

如果有错误则返回NULL,否则返回已分配的迭代器。

可以在flags中传递的标志适用于整个迭代器,它们是:

NPY_ITER_C_INDEX

使迭代器跟踪匹配C order的遍历平面索引。此选项不能与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_INDEXNPY_ITER_F_INDEXNPY_ITER_MULTI_INDEX不兼容。

NPY_ITER_DONT_NEGATE_STRIDES

这只会在为order参数指定NPY_KEEPORDER时影响迭代器。默认情况下,使用NPY_KEEPORDER,迭代器会反转具有负步长的轴,以便以正向方向遍历内存。这将禁用此步骤。如果您想要使用轴的底层内存排序,但不想使轴反转,请使用此标志。这是例如numpy.ravel(a, order ='K')的行为。

NPY_ITER_COMMON_DTYPE

导致迭代器将所有操作数转换为基于ufunc类型升级规则计算的公共数据类型。必须启用复制或缓冲。

如果提前知道公共数据类型,请不要使用此标志。相反,为所有操作数设置请求的dtype。

NPY_ITER_REFS_OK

表示具有引用类型的数组(对象数组或包含对象类型的结构化数组)可以在迭代器中接受和使用。如果启用此标志,调用程序必须确保检查NpyIter_IterationNeedsAPI(iter)是否为真,在这种情况下,它可能不会在迭代期间释放GIL。

NPY_ITER_ZEROSIZE_OK

表示应允许大小为零的数组。由于典型的迭代循环不会自然地使用零大小的数组,因此在进入迭代循环之前,必须检查IterSize是否大于零。目前只检查操作数,而不是强制形状。

NPY_ITER_REDUCE_OK

允许具有零步幅和大小大于1的维度的可写操作数。注意,这样的操作数必须是读/写。

当启用缓冲时,这也会切换到特殊缓冲模式,该模式会根据需要减少循环长度,以便不对正在减少的值进行践踏。

注意,如果你想对一个自动分配的输出进行缩减,你必须使用NpyIter_GetOperandArray来获得它的引用,然后在迭代循环之前将每个值设置为缩减单元。在缓冲减少的情况下,这意味着您还必须指定标志NPY_ITER_DELAY_BUFALLOC,然后在初始化分配的操作数以准备缓冲区后重置迭代器。

NPY_ITER_RANGED

支持对完整iterindex范围[0, NpyIter_IterSize(iter))的子范围的迭代。使用函数NpyIter_ResetToIterIndexRange指定迭代的范围。

只有在启用NPY_ITER_BUFFERED时,此标志才能与NPY_ITER_EXTERNAL_LOOP一起使用。这是因为没有缓冲,内部循环总是最内部迭代维度的大小,并且允许它被削减将需要特殊处理,有效地使它更像缓冲版本。

NPY_ITER_BUFFERED

使迭代器存储缓冲数据,并使用缓冲来满足数据类型,对齐和字节顺序要求。要缓冲操作数,请不要指定NPY_ITER_COPYNPY_ITER_UPDATEIFCOPY标志,因为它们将覆盖缓冲。缓冲对于使用迭代器的Python代码特别有用,允许同时使用更大的数据块来摊销Python解释器的开销。

如果与NPY_ITER_EXTERNAL_LOOP一起使用,调用者的内部循环可能获得比没有缓冲的情况下更大的块,因为如何布置步幅。

注意,如果操作数被赋予标志NPY_ITER_COPYNPY_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 &lt; 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_WRITEONLYNPY_ITER_READWRITE如果op[i]为NULL,则创建具有最终广播维度的新数组,以及与迭代器的迭代次序匹配的布局。

op[i]为NULL时,请求的数据类型op_dtypes[i]也可以为NULL,在这种情况下,它从数组的dtypes自动生成被标记为可读。生成dtype的规则与UFuncs相同。特别注意的是处理所选dtype中的字节顺序。如果只有一个输入,则使用输入的dtype。否则,如果多个输入dtypes组合在一起,输出将按原生字节顺序。

分配了此标志后,调用者可以通过调用NpyIter_GetOperandArray并获取返回的C数组中的第i个对象来检索新的数组。调用者必须调用Py_INCREF来声明对数组的引用。

NPY_ITER_NO_SUBTYPE

对于与NPY_ITER_ALLOCATE一起使用,此标志禁用为输出分配数组子类型,强制其为直线型。

TODO:也许最好引入一个函数NpyIter_GetWrappedOutput并删除这个标志?

NPY_ITER_NO_BROADCAST

确保输入或输出与迭代维度完全匹配。

NPY_ITER_ARRAYMASK

版本1.7中的新功能。

表示此操作数是用于在向应用了NPY_ITER_WRITEMASKED标志的操作数写入时用于选择元素的掩码。只有一个操作数可能应用了NPY_ITER_ARRAYMASK标志。

具有此标志的操作数的数据类型应为NPY_BOOLNPY_MASK或字段都是有效掩码dtype的struct dtype。在后一种情况下,它必须与结构化操作数WRITEMASKED匹配,因为它为该数组的每个字段指定一个掩码。

此标志只影响从缓冲区写回数组。这意味着如果操作数也是NPY_ITER_READWRITENPY_ITER_WRITEONLY,则执行迭代的代码可以写入此操作数以控制哪些元素将不被修改,哪些元素将被修改。当掩码应该是输入掩码的组合时,这是有用的。可以使用NpyMask_Create函数创建掩码值。

NPY_ITER_WRITEMASKED

版本1.7中的新功能。

表示只有具有ARRAYMASK标志的操作数指示的元素才会被迭代修改。一般来说,迭代器不强制这样做,它是由执行迭代的代码遵循该承诺。代码可以使用NpyMask_IsExposed内联函数来测试特定元素上的掩码是否允许写入。

当使用此标志,并且此操作数被缓冲时,这将改变数据如何从缓冲区复制到数组。使用掩蔽复制例程,其仅复制缓冲器中的元素,其中NpyMask_IsExposed从ARRAYMASK操作数中的相应元素返回true。

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_ndimop_axesitershapebuffersize NpyIter_MultiNew

参数oa_ndim在不为零或-1时指定将使用自定义广播进行迭代的维数。如果提供,还可以提供op_axes必须和itershapeop_axes参数让您详细控制操作数数组的轴如何匹配和迭代。op_axes中,您必须向npy_intp类型的oa_ndim -sized数组提供nop的数组。如果op_axes中的条目为NULL,则应用正常的广播规则。op_axes[j][i]中存储op[j]的有效轴,或者表示newaxis的-1。在每个op_axes[j]数组中,可以不重复轴。以下示例是正常广播如何应用于3-D数字组,2-D数字组,1-D数字组和标量。

Note: Before NumPy 1.8 oa_ndim == 0` was used for signalling that that ``op_axes and itershape are unused. 这已被弃用,应替换为-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为零,则使用默认缓冲区大小,否则指定要使用的缓冲区大小。推荐使用2的幂的缓冲器,例如4096或8192。

如果有错误则返回NULL,否则返回已分配的迭代器。

NpyIter* NpyIter_Copy(NpyIter* iter)

创建给定迭代器的副本。此函数主要用于启用数据的多线程迭代。

TODO:将此移动到有关多线程迭代的部分。

多线程迭代的推荐方法是首先创建一个带有标志NPY_ITER_EXTERNAL_LOOPNPY_ITER_RANGEDNPY_ITER_BUFFEREDNPY_ITER_DELAY_BUFALLOC ,以及可能NPY_ITER_GROWINNER为每个线程创建一个这个迭代器的副本(第一个迭代器减去一个)。然后,取迭代索引范围[0, NpyIter_GetIterSize(iter))并将其拆分成任务,例如使用TBB parallel_for循环。当线程获得要执行的任务时,它然后通过调用NpyIter_ResetToIterIndexRange并遍历整个范围来使用其迭代器的副本。

当在多线程代码中使用迭代器或在不持有Python GIL的代码中使用迭代器时,必须注意只调用在该上下文中安全的函数。NpyIter_Copy无法安全地调用没有Python GIL,因为它增加Python引用。通过将errmsg参数作为非NULL传递,可以安全地调用Reset*和一些其他函数,以便函数将传回错误,而不是设置Python异常。

int NpyIter_RemoveAxis(NpyIter * iter,int axis)``

从迭代中删除轴。这需要为迭代器创建设置NPY_ITER_MULTI_INDEX,如果启用缓冲或正在跟踪索引,则不会工作。此函数还将迭代器重置为其初始状态。

这对于设置例如累加循环是有用的。迭代器可以首先使用所有维度创建,包括累积轴,以便输出正确创建。然后,可以删除累积轴,并以嵌套方式进行计算。

警告:此函数可能会更改迭代器的内部存储器布局。必须再次检索来自迭代器的任何缓存函数或指针!迭代器范围也将被重置。

返回NPY_SUCCEEDNPY_FAIL

int NpyIter_RemoveMultiIndex(NpyIter* iter)

如果迭代器正在跟踪多索引,则会剥离它们的支持,并且如果不需要多索引,则可以进行进一步的迭代器优化。此函数还将迭代器重置为其初始状态。

警告:此函数可能会更改迭代器的内部存储器布局。必须再次检索来自迭代器的任何缓存函数或指针!

调用此函数后,NpyIter_HasMultiIndex(iter)将返回false。

返回NPY_SUCCEEDNPY_FAIL

int NpyIter_EnableExternalLoop(NpyIter* iter)

如果调用NpyIter_RemoveMultiIndex,您可能需要启用标志NPY_ITER_EXTERNAL_LOOP此标记不能与NPY_ITER_MULTI_INDEX一起使用,因此提供此函数以在调用NpyIter_RemoveMultiIndex之后启用该功能。此函数还将迭代器重置为其初始状态。

警告:此函数更改迭代器的内部逻辑。必须再次检索来自迭代器的任何缓存函数或指针!

返回NPY_SUCCEEDNPY_FAIL

int NpyIter_Deallocate(NpyIter* iter)

取消分配迭代器对象。这另外释放所有副本,触发UPDATEIFCOPY行为在必要时。

返回NPY_SUCCEEDNPY_FAIL

int NpyIter_Reset(NpyIter* iter, char** errmsg)

在迭代范围的开始处将迭代器重置为其初始状态。

返回NPY_SUCCEEDNPY_FAIL如果errmsg为非NULL,则在返回NPY_FAIL时,不会设置Python异常。相反,* 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_SUCCEEDNPY_FAIL如果errmsg为非NULL,则在返回NPY_FAIL时,不会设置Python异常。相反,* errmsg设置为错误消息。当errmsg是非NULL时,可以安全地调用该函数,而不用保持Python GIL。

int NpyIter_ResetBasePointers(NpyIter *iter, char** baseptrs, char** errmsg)

将迭代器重置为其初始状态,但使用baseptrs中的值替代正在迭代的数组的指针。此函数旨在与op_axes参数一起通过嵌套迭代代码与两个或多个迭代器一起使用。

返回NPY_SUCCEEDNPY_FAIL如果errmsg为非NULL,则在返回NPY_FAIL时,不会设置Python异常。相反,* 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)

调整迭代器以指向multi_index指向的ndim索引。如果未跟踪多索引,索引超出边界或禁用内循环迭代,则返回错误。

返回NPY_SUCCEEDNPY_FAIL

int NpyIter_GotoIndex(NpyIter* iter, npy_intp index)

调整迭代器以指向指定的index如果迭代器被构造为具有标志NPY_ITER_C_INDEX,则index是C阶索引,并且如果迭代器被构造为具有标志NPY_ITER_F_INDEX index是Fortran顺序索引。如果没有正在跟踪的索引,索引超出边界或禁用内循环迭代,则返回错误。

返回NPY_SUCCEEDNPY_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)

调整迭代器以指向指定的iterindexIterIndex是与迭代器的迭代次序匹配的索引。如果iterindex超出边界,启用缓冲或禁用内循环迭代,则返回错误。

返回NPY_SUCCEEDNPY_FAIL

npy_bool NpyIter_HasDelayedBufAlloc(NpyIter* iter)

如果将标志NPY_ITER_DELAY_BUFALLOC传递给迭代器构造函数,并且尚未调用任何复位函数,则返回1,否则为0。

npy_bool NpyIter_HasExternalLoop(NpyIter* iter)

如果调用程序需要处理最内层的一维循环,则返回1,如果调用程序处理所有循环,则返回0。这由构造函数标志NPY_ITER_EXTERNAL_LOOPNpyIter_EnableExternalLoop控制。

npy_bool NpyIter_HasMultiIndex(NpyIter* iter)

如果迭代器是使用NPY_ITER_MULTI_INDEX标志创建的,则返回1,否则返回0。

npy_bool NpyIter_HasIndex(NpyIter* iter)

如果迭代器是使用NPY_ITER_C_INDEXNPY_ITER_F_INDEX标志创建的,则返回1,否则为0。

npy_bool NpyIter_RequiresBuffering(NpyIter* iter)

如果迭代器需要缓冲,则返回1,这在操作数需要转换或对齐时发生,因此不能直接使用。

npy_bool NpyIter_IsBuffered(NpyIter* iter)

如果迭代器是使用NPY_ITER_BUFFERED标志创建的,则返回1,否则返回0。

npy_bool NpyIter_IsGrowInner(NpyIter* iter)

如果迭代器是使用NPY_ITER_GROWINNER标志创建的,则返回1,否则返回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_SUCCEEDNPY_FAIL

PyArray_Descr** NpyIter_GetDescrArray(NpyIter* iter)

这将返回指向正在迭代的对象的nop数据类型描述符的指针。结果指向iter,因此调用者不会获得对Descr的任何引用。

这个指针可以在迭代循环之前缓存,调用iternext将不会改变它。

PyObject** NpyIter_GetOperandArray(NpyIter* iter)

这将返回指向正在迭代的nop操作数PyObject的指针。结果指向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视图的引用,该视图是数组NpyIter_GetOperandArray中的第i个对象的视图,其尺寸和步长与内部优化的迭代模式相匹配。此视图的C阶迭代等效于迭代器的迭代顺序。

例如,如果迭代器创建时使用单个数组作为其输入,并且可以重新排列其所有轴,然后将其折叠为单个重叠迭代,则这将返回一个一维数组的视图。

void NpyIter_GetReadFlags(NpyIter* iter, char* outreadflags)

填充nop标志。如果op[i]可以读取,则将outreadflags[i]设置为1,如果不能读取,则设置为0。

void NpyIter_GetWriteFlags(NpyIter* iter, char* outwriteflags)

填充nop标志。如果op[i]可以写入,则将outwriteflags[i]设置为1,如果不能,则将其设置为0。

int NpyIter_CreateCompatibleStrides(NpyIter* iter, npy_intp itemsize, npy_intp* outstrides)

构建一组步幅,与使用NPY_ITER_ALLOCATE标志创建的输出数组的步幅相同,其中为op_axes传递了NULL。这是针对连续打包的数据,但不一定是C或Fortran顺序。这应与NpyIter_GetShapeNpyIter_GetNDim一起使用,并将标志NPY_ITER_MULTI_INDEX传递到构造函数中。

此函数的用例是在一个或多个维度上匹配迭代器的形状和布局以及粘性。例如,为了生成一个数值渐变的输入值的向量,你传入ndim * itemsize为itemsize,然后添加另一个维度到结束与大小ndim和stride itemsize。要做Hessian矩阵,你做同样的事情,但添加两个维度,或利用对称性,并将它包装到1维与特定的编码。

仅当迭代器正在跟踪多索引并且如果NPY_ITER_DONT_NEGATE_STRIDES用于防止以相反顺序迭代轴时,才可以调用此函数。

如果使用此方法创建数组,只需为每次迭代添加“itemsize”,就会遍历匹配迭代器的新数组。

返回NPY_SUCCEEDNPY_FAIL

npy_bool NpyIter_IsFirstVisit(NpyIter* iter, int iop)

版本1.7中的新功能。

检查这是否是第一次首次查看迭代器指向的指定reduce操作数的元素。该函数为减少操作数和禁用缓冲时返回一个合理的答案。缓冲的非减少操作数的答案可能不正确。

此函数仅用于EXTERNAL_LOOP模式,并且当该模式未启用时将产生一些错误的答案。

如果这个函数返回true,调用者也应该检查操作数的内循环步长,因为如果该步长为0,那么只有第一次访问最内部外部循环的第一个元素。

警告:出于性能原因,'iop'没有进行边界检查,它没有确认'iop'实际上是一个归约操作数,并且未确认启用了EXTERNAL_LOOP模式。这些检查是调用者的责任,应该在任何内部循环之外完成。

Functions For Iteration

NpyIter_IterNextFunc* NpyIter_GetIterNext(NpyIter* iter, char** errmsg)

返回用于迭代的函数指针。可以通过该函数计算函数指针的专用版本,而不是存储在迭代器结构中。因此,为了获得良好的性能,需要将函数指针保存在变量中,而不是为每次循环迭代而检索。

如果有错误,则返回NULL。如果errmsg为非NULL,则在返回NPY_FAIL时,不会设置Python异常。相反,* 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_BUFFEREDNPY_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,则在返回NPY_FAIL时,不会设置Python异常。相反,* 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_INDEXNPY_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的复位之后),调用它以获得可用于选择快速内循环函数的步长。例如,如果stride为0,那意味着内循环总是可以将其值加载到变量中一次,然后在整个循环中使用该变量,或者如果stride等于itemsize,则可以使用该操作数的连续版本。

这个函数可以安全地调用而无需持有Python GIL。

Converting from Previous NumPy Iterators

旧的迭代器API包括PyArrayIter_Check,PyArray_Iter *和PyArray_ITER_ *等函数。多迭代器数组包括PyArray_MultiIter *,PyArray_Broadcast和PyArray_RemoveSmallest。新的迭代器设计用单个对象和关联的API替换所有这些功能。新API的一个目标是,现有迭代器的所有使用都应该可以用新的迭代器替换,而无需大量的工作。在1.6中,主要的例外是邻域迭代器,它在这个迭代器中没有相应的特性。

这里是一个转换表,用于与新的迭代器一起使用的函数:

迭代器函数  
PyArray_IterNew NpyIter_New
PyArray_IterAllButAxis NpyIter_New + axes参数迭代器标志NPY_ITER_EXTERNAL_LOOP
PyArray_BroadcastToShape 不支持(使用多个操作数的支持)。
PyArrayIter_Check 将需要添加这个在Python暴露
PyArray_ITER_RESET NpyIter_Reset
PyArray_ITER_NEXT NpyIter_GetIterNext的函数指针
PyArray_ITER_DATA c:func:NpyIter_GetDataPtrArray
PyArray_ITER_GOTO NpyIter_GotoMultiIndex
PyArray_ITER_GOTO1D NpyIter_GotoIndexNpyIter_GotoIterIndex
PyArray_ITER_NOTDONE iternext函数指针的返回值
多重迭代器函数  
PyArray_MultiIterNew NpyIter_MultiNew
PyArray_MultiIter_RESET NpyIter_Reset
PyArray_MultiIter_NEXT NpyIter_GetIterNext的函数指针
PyArray_MultiIter_DATA NpyIter_GetDataPtrArray
PyArray_MultiIter_NEXTi 不支持(始终锁定步骤迭代)
PyArray_MultiIter_GOTO NpyIter_GotoMultiIndex
PyArray_MultiIter_GOTO1D NpyIter_GotoIndexNpyIter_GotoIterIndex
PyArray_MultiIter_NOTDONE iternext函数指针的返回值
PyArray_Broadcast NpyIter_MultiNew处理
PyArray_RemoveSmallest 迭代器标记NPY_ITER_EXTERNAL_LOOP
其他功能  
PyArray_ConvertToCommonType 迭代器标记NPY_ITER_COMMON_DTYPE