数组迭代器API

1.6 新版功能.

数组迭代器

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

此页记录迭代器的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;
}

迭代器数据类型

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

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 .

任何一个 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 如果启用,它还将在迭代器销毁时使用另一个强制转换将数据复制回。

如果出现错误,则返回空值,否则返回分配的迭代器。

要使迭代器类似于旧的迭代器,应该可以这样做。

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_CASTINGNPY_EQUIV_CASTINGNPY_SAFE_CASTINGNPY_SAME_KIND_CASTINGNPY_UNSAFE_CASTING . 要允许强制转换发生,还必须启用复制或缓冲。

如果 op_dtypes 不是 NULL ,它指定数据类型或 NULL 对于每一个 op[i] .

如果出现错误,则返回空值,否则返回分配的迭代器。

可以传入的标志 flags 应用于整个迭代器的是:

NPY_ITER_C_INDEX

使迭代器跟踪与C顺序匹配的Raveled平面索引。此选项不能用于 NPY_ITER_F_INDEX .

NPY_ITER_F_INDEX

使迭代器跟踪与Fortran顺序匹配的Raveled平面索引。此选项不能用于 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

这只影响迭代器,当 NPY_KEEPORDER 为order参数指定。默认情况下 NPY_KEEPORDER ,迭代器反转具有负跨距的轴,以便向前遍历内存。这将禁用此步骤。如果要使用轴的基础内存顺序,但不希望轴反转,请使用此标志。这是 numpy.ravel(a, order='K') 例如。

NPY_ITER_COMMON_DTYPE

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

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

NPY_ITER_REFS_OK

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

NPY_ITER_ZEROSIZE_OK

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

NPY_ITER_REDUCE_OK

允许具有零步幅且大小大于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] 如果不满足构造函数标志和参数指定的数据类型或对齐要求,则执行。

NPY_ITER_UPDATEIFCOPY

触发器 NPY_ITER_COPY ,并且当数组操作数被标记为要写入并被复制时,会导致将副本中的数据复制回 op[i] 什么时候? NpyIter_Deallocate 被称为。

如果操作数被标记为只写,并且需要一个副本,则将创建一个未初始化的临时数组,然后将其复制回 op[i] 呼唤 NpyIter_Deallocate ,而不是执行不必要的复制操作。

NPY_ITER_NBO
NPY_ITER_ALIGNED
NPY_ITER_CONTIG

使迭代器为 op[i] 它是以本机字节顺序,根据数据类型要求进行对齐、连续或任意组合。

默认情况下,迭代器生成指向所提供数组的指针,这些数组可以对齐或不对齐,并且具有任何字节顺序。如果未启用复制或缓冲,并且操作数数据不满足约束,则会引发错误。

连续约束仅适用于内部循环,连续的内部循环可能具有任意指针更改。

如果请求的数据类型是非本机字节顺序,则NBO标志将覆盖它,并且请求的数据类型将转换为本机字节顺序。

NPY_ITER_ALLOCATE

这是用于输出数组的,并且需要 NPY_ITER_WRITEONLYNPY_ITER_READWRITE 被设定。如果 op[i] 为空,创建具有最终广播维度的新数组,以及与迭代器的迭代顺序匹配的布局。

什么时候? op[i] 为空,请求的数据类型 op_dtypes[i] 也可以为空,在这种情况下,它是由标记为可读的数组的数据类型自动生成的。生成数据类型的规则与UFUNC相同。特别要注意的是处理所选数据类型中的字节顺序。如果只有一个输入,则按原样使用输入的数据类型。否则,如果将多个输入数据类型组合在一起,则输出将以本机字节顺序进行。

使用此标志分配后,调用方可以通过调用 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 或其字段均为有效掩码数据类型的结构数据类型。在后一种情况下,它必须与被写屏蔽的结构操作数匹配,因为它为该数组的每个字段指定了一个屏蔽。

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

NPY_ITER_WRITEMASKED

1.7 新版功能.

这个阵列是所有人的面具 writemasked 操作数。代码使用 writemasked 标志,指示仅写入选定的ARRAYMASK操作数为真的元素。一般来说,迭代器并不强制这样做,而是由执行迭代的代码来遵循这个承诺。

什么时候? 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/空值传递给 oa_ndimop_axesitershapebuffersize ,相当于 NpyIter_MultiNew .

参数 oa_ndim 当不是零或-1时,指定将使用自定义广播迭代的维度数。如果提供, op_axes 必须和 itershape 也可以提供。这个 op_axes 参数允许您详细控制操作数数组的轴如何匹配和迭代。在 op_axes ,必须提供 nop 指针指向 oa_ndim -按类型调整数组大小 npy_intp . 如果一个条目 op_axes 为空,将应用正常广播规则。在 op_axes[j][i] 存储的有效轴 op[j] 或者-1,意思是 newaxis . 在每一个 op_axes[j] 数组,轴不能重复。下面的示例是普通广播如何应用于三维数组、二维数组、一维数组和标量。

Note :在numpy 1.8之前 oa_ndim == 0` was used for signalling that that `` 斧轴''和 ``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 为零,将使用默认缓冲区大小,否则将指定要使用的缓冲区的大小。建议使用2次幂的缓冲器,如4096或8192。

如果出现错误,则返回空值,否则返回分配的迭代器。

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引用。这个 Reset* 并且通过传入 errmsg 参数为非空,这样函数将通过它返回错误,而不是设置python异常。

NpyIter_Deallocate 必须为每个副本调用。

int NpyIter_RemoveAxis(NpyIter *iter, int axis)

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

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

WARNING :此函数可以更改迭代器的内部内存布局。必须再次检索迭代器中的任何缓存函数或指针!迭代器范围也将被重置。

返回 NPY_SUCCEEDNPY_FAIL .

int NpyIter_RemoveMultiIndex(NpyIter *iter)

如果迭代器正在跟踪一个多索引,那么这将去掉对它们的支持,并在不需要多索引的情况下进一步进行迭代器优化。此函数还将迭代器重置为其初始状态。

WARNING :此函数可以更改迭代器的内部内存布局。必须再次检索迭代器中的任何缓存函数或指针!

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

返回 NPY_SUCCEEDNPY_FAIL .

int NpyIter_EnableExternalLoop(NpyIter *iter)

如果 NpyIter_RemoveMultiIndex 已调用,您可能需要启用标志 NPY_ITER_EXTERNAL_LOOP . 此标志不允许与 NPY_ITER_MULTI_INDEX ,因此提供此函数是为了在 NpyIter_RemoveMultiIndex 被称为。此函数还将迭代器重置为其初始状态。

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

返回 NPY_SUCCEEDNPY_FAIL .

int NpyIter_Deallocate(NpyIter *iter)

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

返回 NPY_SUCCEEDNPY_FAIL .

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

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

返回 NPY_SUCCEEDNPY_FAIL . 如果errmsg非空,则在以下情况下不设置python异常: NPY_FAIL 返回。相反, * errmsg设置为错误消息。当errmsg不为空时,可以安全地调用函数,而不保留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非空,则在以下情况下不设置python异常: NPY_FAIL 返回。相反, * errmsg设置为错误消息。当errmsg不为空时,可以安全地调用函数,而不保留python gil。

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

将迭代器重置回其初始状态,但使用中的值 baseptrs 用于数据而不是正在迭代的数组中的指针。此功能与 op_axes 参数,通过具有两个或更多迭代器的嵌套迭代代码。

返回 NPY_SUCCEEDNPY_FAIL . 如果errmsg非空,则在以下情况下不设置python异常: NPY_FAIL 返回。相反, * errmsg设置为错误消息。当errmsg不为空时,可以安全地调用函数,而不保留python gil。

TODO :将以下内容移动到嵌套迭代器的特殊部分中。

为嵌套迭代创建迭代器需要一些注意。所有迭代器操作数必须完全匹配,或者调用 NpyIter_ResetBasePointers 将无效。这意味着自动拷贝和输出分配不应随意使用。仍然可以使用迭代器的自动数据转换和强制转换功能,方法是创建一个启用了所有转换参数的迭代器,然后使用 NpyIter_GetOperandArray 函数,并将其传递给其余迭代器的构造函数。

WARNING :为嵌套迭代创建迭代器时,代码在不同迭代器中不能多次使用维度。如果这样做,嵌套迭代将在迭代期间生成越界指针。

WARNING :为嵌套迭代创建迭代器时,缓冲只能应用于最内层迭代器。如果将缓冲迭代器用作 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)

调整迭代器以指向 ndim 所指指数 multi_index . 如果未跟踪多索引、索引超出界限或内部循环迭代被禁用,则返回错误。

返回 NPY_SUCCEEDNPY_FAIL .

int NpyIter_GotoIndex(NpyIter *iter, npy_intp index)

调整迭代器以指向 index 明确规定。如果迭代器是用标志构造的 NPY_ITER_C_INDEXindex 是C顺序索引,如果迭代器是用标志构造的 NPY_ITER_F_INDEXindex 是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 已传递给迭代器构造函数,但尚未调用其中一个重置函数,否则为0。

npy_bool NpyIter_HasExternalLoop(NpyIter *iter)

如果调用方需要处理最内部的一维循环,则返回1;如果迭代器处理所有循环,则返回0。这由构造函数标志控制 NPY_ITER_EXTERNAL_LOOPNpyIter_EnableExternalLoop .

npy_bool NpyIter_HasMultiIndex(NpyIter *iter)

如果迭代器是用 NPY_ITER_MULTI_INDEX 标志,否则为0。

npy_bool NpyIter_HasIndex(NpyIter *iter)

如果迭代器是用 NPY_ITER_C_INDEXNPY_ITER_F_INDEX 标志,否则为0。

npy_bool NpyIter_RequiresBuffering(NpyIter *iter)

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

npy_bool NpyIter_IsBuffered(NpyIter *iter)

如果迭代器是用 NPY_ITER_BUFFERED 标志,否则为0。

npy_bool NpyIter_IsGrowInner(NpyIter *iter)

如果迭代器是用 NPY_ITER_GROWINNER 标志,否则为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 正在迭代的对象的数据类型说明。结果指向 iter ,因此调用方不会获得对描述的任何引用。

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

PyObject **NpyIter_GetOperandArray(NpyIter *iter)

这将返回一个指向 nop 正在迭代的操作数pyobjects。结果指向 iter ,因此调用方不会获得对PyObjects的任何引用。

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_轴传递了空值。这适用于连续打包的数据,但不一定按C或Fortran顺序打包。这个应该和 NpyIter_GetShapeNpyIter_GetNDim 带旗 NPY_ITER_MULTI_INDEX 传递给构造函数。

这个函数的一个用例是匹配迭代器的形状和布局,并定位在一个或多个维度上。例如,为了为数值渐变的每个输入值生成一个向量,您为itemsize传递ndim*itemsize,然后将另一个维度添加到具有大小ndim和stride itemsize的末尾。要做黑森矩阵,你做同样的事情,但是添加两个维度,或者利用对称性,用一个特定的编码将其打包成一维。

只有迭代器跟踪多个索引并且如果 NPY_ITER_DONT_NEGATE_STRIDES 用于防止轴按相反顺序迭代。

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

返回 NPY_SUCCEEDNPY_FAIL .

npy_bool NpyIter_IsFirstVisit(NpyIter *iter, int iop)

1.7 新版功能.

检查这是否是第一次看到迭代器指向的指定缩减操作数的元素。当禁用缓冲时,函数返回还原操作数的合理答案。对于缓冲的非还原操作数,答案可能不正确。

此功能仅用于外部_循环模式,在该模式未启用时将产生一些错误的答案。

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

WARNING :由于性能原因,未检查“IOP”的边界,未确认“IOP”实际上是缩减操作数,也未确认启用了外部_循环模式。这些检查是调用者的责任,应该在任何内部循环之外进行。

迭代函数

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

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

如果出现错误,则返回空值。如果errmsg非空,则在以下情况下不设置python异常: NPY_FAIL 返回。相反, * errmsg设置为错误消息。当errmsg不为空时,可以安全地调用函数,而不保留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)

返回一个函数指针,用于获取迭代器的当前多索引。如果迭代器不跟踪多索引,则返回空值。建议在迭代循环之前将此函数指针缓存在局部变量中。

如果出现错误,则返回空值。如果errmsg非空,则在以下情况下不设置python异常: NPY_FAIL 返回。相反, * errmsg设置为错误消息。当errmsg不为空时,可以安全地调用函数,而不保留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)

这会返回一个指向被跟踪索引的指针,如果没有跟踪索引,则返回空值。只有当其中一个标志 NPY_ITER_C_INDEXNPY_ITER_F_INDEX 在施工期间指定。

当旗帜 NPY_ITER_EXTERNAL_LOOP 使用时,代码需要知道执行内部循环的参数。这些功能提供了这些信息。

npy_intp *NpyIter_GetInnerStrideArray(NpyIter *iter)

返回指向 nop 步幅,每个迭代对象一个,供内部循环使用。

此指针可以在迭代循环之前缓存,调用 iternext 不会改变它。在不保存python gil的情况下,可以安全地调用此函数。

WARNING :当指针可以被缓存时,如果迭代器被缓存,它的值可能会改变。

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包含诸如pyarray iter_check、pyarray_iter等函数 * and PyArray_ITER_* . 多迭代器数组包括pyarray_muliter*、pyarray_broadcast和pyarray_removesmallest。新的迭代器设计将所有这些功能替换为单个对象和关联的API。新的API的一个目标是,现有迭代器的所有使用都应该可以用新的迭代器替换,而不需要付出很大的努力。在1.6中,最大的例外是邻域迭代器,它在这个迭代器中没有相应的特性。

下面是要与新迭代器一起使用的函数的转换表:

迭代器函数

PyArray_IterNew

NpyIter_New

PyArray_IterAllButAxis

NpyIter_New + axes parameter or Iterator flag NPY_ITER_EXTERNAL_LOOP

PyArray_BroadcastToShape

不支持 (使用对多个操作数的支持。)

PyArrayIter_Check

需要在python exposure中添加这个

PyArray_ITER_RESET

NpyIter_Reset

PyArray_ITER_NEXT

函数指针来自 NpyIter_GetIterNext

PyArray_ITER_DATA

NpyIter_GetDataPtrArray

PyArray_ITER_GOTO

NpyIter_GotoMultiIndex

PyArray_ITER_GOTO1D

NpyIter_GotoIndex or NpyIter_GotoIterIndex

PyArray_ITER_NOTDONE

的返回值 iternext 函数指针

Multi-iterator Functions

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_GotoIndex or NpyIter_GotoIterIndex

PyArray_MultiIter_NOTDONE

的返回值 iternext 函数指针

PyArray_Broadcast

被处理 NpyIter_MultiNew

PyArray_RemoveSmallest

迭代器旗标 NPY_ITER_EXTERNAL_LOOP

其他功能

PyArray_ConvertToCommonType

迭代器旗标 NPY_ITER_COMMON_DTYPE