数组迭代器 API#

数组迭代器#

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

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

有一个 introductory guide to array iteration ,那些使用这个 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 的类型.目前,没有 API 提供对 Python 创建的迭代器值的访问.如果在 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 .

NPY_ORDER 枚举的任何值都可以传递给 order .为了高效迭代, NPY_KEEPORDER 是最佳选择,其他顺序强制执行特定的迭代模式.

NPY_CASTING 枚举的任何值都可以传递给 casting .这些值包括 NPY_NO_CASTING , NPY_EQUIV_CASTING , NPY_SAFE_CASTING , NPY_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)#

创建用于广播在 op 中提供的 nop 数组对象的迭代器,使用常规 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_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_INDEX , NPY_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.如果您正在使用已知的 dtypes, NpyIter_GetTransferFlags 是一种更快更精确的方式来检查迭代器是否需要缓冲导致 API.

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_EXTERNAL_LOOP 时,此标志才能与 NPY_ITER_BUFFERED 一起使用. 这是因为在没有缓冲的情况下,内部循环始终是最内层迭代维度的大小,并且允许将其切断需要特殊处理,实际上使其更像缓冲版本.

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] 不符合构造函数标志和参数指定的数据类型或对齐要求,则允许复制 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,在这种情况下,它会自动从标记为可读取的数组的 dtype 生成. 用于生成 dtype 的规则与 UFuncs 的规则相同. 特别值得注意的是所选 dtype 中字节顺序的处理. 如果只有一个输入,则按原样使用输入的 dtype. 否则,如果将多个输入 dtype 组合在一起,则输出将采用本机字节顺序.

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

NPY_ITER_NO_SUBTYPE#

NPY_ITER_ALLOCATE 一起使用,此标志禁用为输出分配数组子类型,强制输出为一个直接的 ndarray.

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

NPY_ITER_NO_BROADCAST#

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

NPY_ITER_ARRAYMASK#

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

具有此标志的操作数的数据类型应为 NPY_BOOL , NPY_MASK 或者其字段均为有效掩码类型的结构 dtype. 在后一种情况下,它必须与正在 WRITEMASKED 的结构操作数匹配,因为它正在为该数组的每个字段指定一个掩码.

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

NPY_ITER_WRITEMASKED#

此数组是所有 writemasked 操作数的掩码.代码使用 writemasked 标志,该标志指示仅当选定的 ARRAYMASK 操作数为 True 时,才会将元素写入.通常,迭代器不强制执行此操作,由进行迭代的代码来遵守该承诺.

当使用 writemasked 标志,并且此操作数被缓冲时,这将改变数据从缓冲区复制到数组的方式.使用掩码复制例程,它仅复制缓冲区中 writemasked 从 ARRAYMASK 操作数中的相应元素返回 true 的元素.

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 ,以提供对广播和缓冲的更多控制.

如果将 -1/NULL 值传递给 oa_ndim , op_axes , itershapebuffersize ,则等效于 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-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 为零,则使用默认缓冲区大小,否则它指定要使用的缓冲区的大小. 建议使用 4096 或 8192 这样的 2 的幂的缓冲区.

如果有错误,则返回 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 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 .

NPY_ARRAYMETHOD_FLAGS NpyIter_GetTransferFlags(NpyIter *iter)#

在 2.3 版本加入.

获取 NPY_METH_RUNTIME_FLAGS ,它提供有关缓冲是否需要 Python GIL ( NPY_METH_REQUIRES_PYAPI ) 或是否可以设置浮点错误的 ( NPY_METH_NO_FLOATINGPOINT_ERRORS ) 信息.

在 NumPy 2.3 之前,可用的公共函数是 NpyIter_IterationNeedsAPI ,它仍然可用,并且还检查对象(或类似)dtype,而不仅仅是缓冲/迭代本身的需求.通常,应该首选此函数.

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 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 标志创建的输出数组的步长相同的步长,其中为 op_axes 传递了 NULL. 这是为了连续打包的数据,但不一定是以 C 或 Fortran 顺序. 这应该与 NpyIter_GetShapeNpyIter_GetNDim 一起使用,并将标志 NPY_ITER_MULTI_INDEX 传递到构造函数中.

此函数的一个用例是匹配迭代器的形状和布局,并附加一个或多个维度. 例如,为了为数值梯度生成每个输入值的向量,您可以传入 itemsize 的 ndimitemsize,然后在末尾添加另一个维度,大小为 ndim,步长为 itemsize. 要执行 Hessian 矩阵,您可以执行相同的操作,但添加两个维度,或者利用对称性并将其打包到一个具有特定编码的维度中.

仅当迭代器正在跟踪多重索引并且使用了 NPY_ITER_DONT_NEGATE_STRIDES 来防止轴以相反的顺序迭代时,才可以调用此函数.

如果使用此方法创建数组,则仅为每次迭代添加“itemsize”将遍历与迭代器匹配的新数组.

返回 NPY_SUCCEEDNPY_FAIL .

npy_bool NpyIter_IsFirstVisit(NpyIter *iter, int iop)#

检查迭代器指向的特定归约操作数的元素是否是第一次被看到.该函数对于归约操作数以及禁用缓冲时返回合理的结果. 对于缓冲的非归约操作数,答案可能不正确.

此函数仅用于 EXTERNAL_LOOP 模式,并且在该模式未启用时会产生一些错误答案.

如果此函数返回 true,则调用者还应检查操作数的内部循环步幅,因为如果该步幅为 0,则仅访问最内层外部循环的第一个元素.

警告:出于性能原因,不对 ‘iop’ 进行边界检查,不确认 ‘iop’ 实际上是一个归约操作数,并且不确认 EXTERNAL_LOOP 模式已启用. 这些检查是调用者的责任,应在任何内部循环之外完成.

用于迭代的函数#

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_ITER_DELAY_BUFALLOC 之后进行了重置),调用此方法获取可用于选择快速内部循环函数的步长. 例如,如果步长为0,则表示内部循环可以始终将其值加载到变量中一次,然后在整个循环中使用该变量,或者如果步长等于itemsize,则可以使用该操作数的连续版本.

可以在不持有 Python GIL 的情况下安全地调用此函数.

从以前的NumPy迭代器转换#

旧的迭代器API包括诸如PyArrayIter_Check,PyArray_Iter和PyArray_ITER_*之类的函数. 多迭代器数组包括PyArray_MultiIter,PyArray_Broadcast和PyArray_RemoveSmallest. 新的迭代器设计用单个对象和关联的API替代了所有这些功能. 新API的目标之一是,现有迭代器的所有用法都应可以用新的迭代器替换,而无需付出很大的努力. 在1.6中,主要的例外是邻域迭代器,该迭代器在此迭代器中没有相应的功能.

这是一个转换表,用于说明新的迭代器应使用哪些函数: