数组迭代器 API#

1.6 版新增。

数组迭代器#

数组迭代器囊括了 ufunc 中的许多关键特征,支持用户代码使用诸如输出参数、保留内存布局和缓冲对齐方式或类型错误的数据等特征,而无需编写复杂的代码。

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

有一个 数组迭代简介,可能对使用此 C API 的用户有用。在许多情况下,在编写 C 迭代代码前在 Python 中创建迭代器来测试想法是个好主意。

迭代示例#

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

多索引跟踪示例#

此示例展示了如何使用 NPY_ITER_MULTI_INDEX 标志。为简单起见,我们假设参数是一个二维数组。

int PrintMultiIndex(PyArrayObject *arr) {
    NpyIter *iter;
    NpyIter_IterNextFunc *iternext;
    npy_intp multi_index[2];

    iter = NpyIter_New(
        arr, NPY_ITER_READONLY | NPY_ITER_MULTI_INDEX | NPY_ITER_REFS_OK,
        NPY_KEEPORDER, NPY_NO_CASTING, NULL);
    if (iter == NULL) {
        return -1;
    }
    if (NpyIter_GetNDim(iter) != 2) {
        NpyIter_Deallocate(iter);
        PyErr_SetString(PyExc_ValueError, "Array must be 2-D");
        return -1;
    }
    if (NpyIter_GetIterSize(iter) != 0) {
        iternext = NpyIter_GetIterNext(iter, NULL);
        if (iternext == NULL) {
            NpyIter_Deallocate(iter);
            return -1;
        }
        NpyIter_GetMultiIndexFunc *get_multi_index =
            NpyIter_GetGetMultiIndex(iter, NULL);
        if (get_multi_index == NULL) {
            NpyIter_Deallocate(iter);
            return -1;
        }

        do {
            get_multi_index(iter, multi_index);
            printf("multi_index is [%" NPY_INTP_FMT ", %" NPY_INTP_FMT "]\n",
                   multi_index[0], multi_index[1]);
        } while (iternext(iter));
    }
    if (!NpyIter_Deallocate(iter)) {
        return -1;
    }
    return 0;
}

当使用一个 2x3 数组调用时,以上示例会打印

multi_index is [0, 0]
multi_index is [0, 1]
multi_index is [0, 2]
multi_index is [1, 0]
multi_index is [1, 1]
multi_index is [1, 2]

迭代器数据类型#

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

type NpyIter#

这是迭代器的静态指针类型。只能通过迭代器 API 访问其内容。

type NpyIter_Type#

这是将迭代器公开给 Python 的类型。当前,尚未公开提供访问 Python 创建的迭代器值的 API。如果迭代器是在 Python 中创建的,则必须在 Python 中使用,反之亦然。此类 API 可能会在未来版本中创建。

type NpyIter_IterNextFunc#

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

type 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 除外。

可向 order 传递任意 NPY_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 顺序相匹配的展开的扁平索引。此选项不能与 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) 到底是 true 还是 false,在这种情况下,在迭代期间它可能不会释放 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 来分配缓冲区并用其初始值填充缓冲区。

NPY_ITER_COPY_IF_OVERLAP#

如果任何写操作数与任何读操作数重合,则通过制作临时副本(如需要,为写操作数启用 UPDATEIFCOPY)来消除所有重合。如果存在包含两个数组公用数据的内存地址,则一对操作数有重合。

由于精确重合检测在维度数量上的运行时间呈指数级,因此该决策基于试探法做出,该试探法有误报(在异常情况下需要无用副本)但没有漏报。

如果存在任何读/写重合,此标志确保操作结果与所有操作数都已复制的情况相同。在需要制作副本的情况下,如果没有此标志,则计算结果可能未定义!

可在 op_flags[i] 中传递的标志,其中 0 <= i < nop

NPY_ITER_READWRITE#
NPY_ITER_READONLY#
NPY_ITER_WRITEONLY#

指示迭代器的用户将如何读或写到 op[i]。对于每个操作数必须指定其中一个标志。将 NPY_ITER_READWRITENPY_ITER_WRITEONLY 用于用户提供的操作数可能会触发 WRITEBACKIFCOPY 语义。当调用 NpyIter_Deallocate 时,数据将被写回原始数组。

NPY_ITER_COPY#

允许创建 op[i] 的副本,如果它不满足构造函数标志和参数指定的的数据类型或对齐要求。

NPY_ITER_UPDATEIFCOPY#

触发 NPY_ITER_COPY,当数组操作数被标记为写并已复制时,会导致副本中的数据在调用NpyIter_Deallocate 时被复制回 op[i]

如果操作数已标记为只写,且需要一个副本,将创建一个未初始化的临时数组,然后在调用 NpyIter_Deallocate 时将其复制回 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 配合使用,此标志禁用为输出分配数组子类型,强制其成为一个直接 ndarray。

待办:也许引入函数 NpyIter_GetWrappedOutput 并删除此标志会更好?

NPY_ITER_NO_BROADCAST#

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

NPY_ITER_ARRAYMASK#

新版 1.7 中添加。

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

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

此标志仅影响从缓冲区写回数组的操作。这意味着如果操作数同时是 NPY_ITER_READWRITENPY_ITER_WRITEONLY,则执行迭代的代码可以写入此操作数以控制哪些元素保持不变,哪些元素将被修改。当掩码应该是输入掩码的组合时,这么做非常有用。

NPY_ITER_WRITEMASKED#

新版 1.7 中添加。

此阵列是所有 writemasked 操作数的掩码。代码使用 writemasked 标志,指示仅向选定的 ARRAYMASK 操作数为 True 的元素写入。通常,迭代器不会强制执行此操作,具体取决于执行迭代的代码来实现此承诺。

当使用 writemasked 标志,并且此操作数已缓冲,这样会更改从缓冲区复制数据到阵列中的方式。使用掩码复制例程,仅复制缓冲区中 ARRAYMASK 操作数中相应元素返回 True 的元素。writemasked

NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE#

在内存重叠检查中,假设仅按迭代器顺序访问启用了 NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE 的操作数。

这使迭代器能够推断数据依赖性,可能避免不必要的复制。

仅当迭代器上启用了 NPY_ITER_COPY_IF_OVERLAP 时,此标志才有效。

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 const *itershape, npy_intp buffersize)#

使用更高级别的选项扩展 NpyIter_MultiNew,以便更好地控制广播和缓冲。

如果向 oa_ndimop_axesitershapebuffersize 传递 -1/NULL 值,则等同于 NpyIter_MultiNew

当参数 oa_ndim 非0或-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-D 数组, 一个 2-D 数组, 一个 1-D 数组和一个标量。

注意: 在 NumPy 1.8 之前, oa_ndim == 0 用于表示 op_axesitershape 未使用。这已过时, 并且应替换为 -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 为 0, 则使用默认缓冲区大小, 否则它将指定要使用多大缓冲区。建议使用 4096 或 8192 等 2 的幂的缓冲区。

如果有错误,将返回 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 异常。

NpyIter_Deallocate 必须针对每个副本调用。

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

释放迭代器对象并解决任何需要的回写。

返回 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 就可以安全地调用该函数。

待办事项:将以下内容移至嵌套迭代器的特殊部分。

为嵌套迭代创建迭代器需要小心。所有迭代器操作数必须完全匹配,否则对 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 const *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)#

调整迭代器以使其指向指定的 iterindex。IterIndex 是一个与迭代器的迭代顺序相匹配的索引。如果 iterindex 超出界限,启用缓存,或者禁用内部循环迭代,则返回错误。

返回 NPY_SUCCEEDNPY_FAIL

npy_bool NpyIter_HasDelayedBufAlloc(NpyIter *iter)#

如果标志 NPY_ITER_DELAY_BUFALLOC 已传递给迭代器构造函数且尚未调用任一 Reset 函数,则返回 1,否则返回 0。

npy_bool NpyIter_HasExternalLoop(NpyIter *iter)#

如果调用者需要处理最内部的 1 维循环,则返回 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_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 数据类型 Descrs 的指针。结果指向 iter,因此调用方不会获得对 Descrs 任何引用。

可以在迭代循环之前缓存此指针,调用 iternext 不会更改它。

PyObject **NpyIter_GetOperandArray(NpyIter *iter)#

返回指向正在迭代的nop操作数PyObjects的指针。结果指向iter,因此调用者不会获得对PyObjects的任何引用。

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 标记创建的输出数组的步长相同,其中 NULL 传给了 op_axes。这适用于以连续方式打包的数据,而不一定是 C 或 Fortran 顺序。这应该与 NpyIter_GetShapeNpyIter_GetNDim 一起使用,并传入 NPY_ITER_MULTI_INDEX 标记到构造函数中。

此函数的一个用例是匹配迭代器的形状和布局,并附加一个或多个维度。例如,为了针对数值梯度为每个输入值生成一个向量,则为 itemsize 传入 ndim*itemsize,接着在末尾添加另一个尺寸,大小为 ndim,步长为 itemsize。要执行 Hessian 矩阵,执行相同操作,但添加两个维度,或者利用对称性将其打包到 1 个维度并采用特定编码。

如果迭代器跟踪多重索引,并且 NPY_ITER_DONT_NEGATE_STRIDES 用于防止轴以相反顺序进行迭代时,才调用此函数。

如果使用此方法创建数组,则只需为每次迭代添加“项目大小”,就可以遍历与迭代器匹配的新数组。

返回 NPY_SUCCEEDNPY_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,则当返回 NPY_FAIL 时会设置 *errmsg 为错误消息,而不是引发 Python 异常。当 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 均为 true),则内部大小也可以用作信号。当 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 时会设置 *errmsg 为错误消息,而不是引发 Python 异常。当 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_ITER_DELAY_BUFALLOC 被使用的话,在重置之后),调用这个来获取可以用来选择一个快速的内部循环函数的步长。例如,如果步长为 0,这意味着内部循环可以始终将它的值加载为一个变量一次,在整个循环中使用这个变量,或者如果步长等于项目大小,这个操作数的连续版本可能被使用。

这个函数可以在不持有 Python GIL 的情况下安全地调用。

从之前的 NumPy 迭代器转换

旧的迭代器 API 包括 PyArrayIter_Check、PyArray_Iter* 和 PyArray_ITER_* 等函数。多迭代器数组包括 PyArray_MultiIter*、PyArray_Broadcast 和 PyArray_RemoveSmallest。新迭代器设计用一个对象及关联的 API 替换了所有这些功能。新 API 的一个目标是,所有使用现有迭代器都可以轻松替换为新迭代器,而无须付出很大努力。1.6 中的主要例外是邻域迭代器,它不具有此迭代器中的相应特征。

voici les fonctions à utiliser avec le nouvel itérateur :