NumPy参考 >NumPy C-API > 数组迭代器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;
}
迭代器布局是内部细节,并且用户代码仅看到不完整的结构。
NpyIter
¶这是迭代器的不透明指针类型。只能通过迭代器API来访问其内容。
NpyIter_Type
¶这是将迭代器公开给Python的类型。当前,没有公开提供访问Python创建的迭代器值的API。如果迭代器是在Python中创建的,则必须在Python中使用,反之亦然。这样的API可能会在将来的版本中创建。
NpyIter_IterNextFunc
¶这是的迭代循环的函数指针,由返回
NpyIter_GetIterNext
。
NpyIter_GetMultiIndexFunc
¶这是用于获取由返回的当前迭代器多索引的函数指针NpyIter_GetGetMultiIndex
。
NpyIter_New
(PyArrayObject * 运,npy_uint32 标志,NPY_ORDER 秩序,NPY_CASTING 铸造,PyArray_Descr * D型)¶为给定的numpy数组对象创建一个迭代器op
。
可以传入的标志是flags
中记录的global和per-operand标志的任意组合
NpyIter_MultiNew
,但除外NPY_ITER_ALLOCATE
。
任何NPY_ORDER
枚举值都可以传递给order
。对于有效的迭代,NPY_KEEPORDER
是最好的选择,其他命令强制执行特定的迭代模式。
任何NPY_CASTING
枚举值都可以传递给casting
。该值包括NPY_NO_CASTING
,NPY_EQUIV_CASTING
,
NPY_SAFE_CASTING
,NPY_SAME_KIND_CASTING
,和
NPY_UNSAFE_CASTING
。要允许进行强制转换,还必须启用复制或缓冲。
如果dtype
不是NULL
,则需要该数据类型。如果允许复制,则在数据可转换时将进行临时复制。如果NPY_ITER_UPDATEIFCOPY
启用,它还会在销毁迭代器时将数据复制回另一个强制类型转换。
如果有错误,则返回NULL,否则返回分配的迭代器。
为了使迭代器类似于旧的迭代器,这应该可以工作。
iter = NpyIter_New(op, NPY_ITER_READWRITE,
NPY_CORDER, NPY_NO_CASTING, NULL);
如果要使用对齐的double
代码编辑数组,但顺序无关紧要,请使用此方法。
dtype = PyArray_DescrFromType(NPY_DOUBLE);
iter = NpyIter_New(op, NPY_ITER_READWRITE|
NPY_ITER_BUFFERED|
NPY_ITER_NBO|
NPY_ITER_ALIGNED,
NPY_KEEPORDER,
NPY_SAME_KIND_CASTING,
dtype);
Py_DECREF(dtype);
NpyIter_MultiNew
(npy_intp nop,PyArrayObject ** op,npy_uint32 标志,NPY_ORDER 顺序,NPY_CASTING 转换,npy_uint32 * op_flags,PyArray_Descr ** op_dtypes )¶使用常规的NumPy广播规则创建一个迭代器来广播所nop
提供的数组对象op
。
任何NPY_ORDER
枚举值都可以传递给order
。对于有效的迭代,NPY_KEEPORDER
是最好的选择,其他命令强制执行特定的迭代模式。使用时
NPY_KEEPORDER
,如果您还想确保迭代不沿轴反转,则应传递标记
NPY_ITER_DONT_NEGATE_STRIDES
。
任何NPY_CASTING
枚举值都可以传递给casting
。该值包括NPY_NO_CASTING
,NPY_EQUIV_CASTING
,
NPY_SAFE_CASTING
,NPY_SAME_KIND_CASTING
,和
NPY_UNSAFE_CASTING
。要允许进行强制转换,还必须启用复制或缓冲。
如果op_dtypes
不是NULL
,则指定数据类型或NULL
每种类型op[i]
。
如果有错误,则返回NULL,否则返回分配的迭代器。
flags
适用于整个迭代器的标志可能是:
NPY_ITER_C_INDEX
¶使迭代器跟踪与C顺序匹配的零散平面索引。此选项不能与一起使用
NPY_ITER_F_INDEX
。
NPY_ITER_F_INDEX
¶使迭代器跟踪与Fortran顺序匹配的混乱平面索引。此选项不能与一起使用
NPY_ITER_C_INDEX
。
NPY_ITER_MULTI_INDEX
¶使迭代器跟踪多索引。这样可以防止迭代器合并轴以产生更大的内部循环。如果循环也没有缓冲并且没有索引被跟踪(可以调用NpyIter_RemoveAxis),则迭代器的大小可以
-1
指示迭代器太大。这可能是由于复杂的广播而发生的,并且在设置迭代器范围,删除多重索引或获取下一个功能时会导致创建错误。但是,如果移除后尺寸足够小,则可以再次移除轴并正常使用迭代器。
NPY_ITER_EXTERNAL_LOOP
¶使迭代器跳过最内层循环的迭代,要求迭代器的用户进行处理。
该标志是不兼容的
NPY_ITER_C_INDEX
,NPY_ITER_F_INDEX
和NPY_ITER_MULTI_INDEX
。
NPY_ITER_DONT_NEGATE_STRIDES
¶仅当
NPY_KEEPORDER
为order参数指定时,这才影响迭代器。默认情况下,使用NPY_KEEPORDER
,迭代器反转具有负步幅的轴,以便沿正向遍历内存。这将禁用此步骤。如果要使用轴的基础内存顺序,但不希望轴反转,请使用此标志。例如,这就是行为 。numpy.ravel(a, order='K')
NPY_ITER_COMMON_DTYPE
¶使迭代器将所有操作数转换为基于ufunc类型提升规则计算的通用数据类型。必须启用复制或缓冲。
如果公共数据类型提前知道,则不要使用此标志。而是为所有操作数设置请求的dtype。
NPY_ITER_REFS_OK
¶指示在迭代器中可以接受和使用具有引用类型的数组(对象数组或包含对象类型的结构化数组)。如果启用了此标志,则调用者必须确保检查是否
NpyIter_IterationNeedsAPI(iter)
为true,在这种情况下,它可能不会在迭代过程中释放GIL。
NPY_ITER_ZEROSIZE_OK
¶指示应允许大小为零的数组。由于典型的迭代循环自然无法与零大小的数组一起使用,因此在进入迭代循环之前,必须检查IterSize是否大于零。当前仅检查操作数,而不检查强制形状。
NPY_ITER_REDUCE_OK
¶允许可写操作数的步长为零且大小大于一。请注意,此类操作数必须是读/写的。
启用缓冲后,这还将切换到特殊的缓冲模式,该模式会根据需要减小循环长度,以免踩踏所减小的值。
请注意,如果要对自动分配的输出进行约简,则必须使用
NpyIter_GetOperandArray
获取其参考,然后在进行迭代循环之前将每个值设置为约简单位。对于缓冲减少,这意味着您还必须指定标志NPY_ITER_DELAY_BUFALLOC
,然后在初始化分配的操作数以准备缓冲区之后重置迭代器。
NPY_ITER_RANGED
¶支持对整个
iterindex
范围的子范围进行迭代 。使用该函数指定迭代范围。[0, NpyIter_IterSize(iter))
NpyIter_ResetToIterIndexRange
此标志只能与
NPY_ITER_EXTERNAL_LOOP
何时启用一起使用NPY_ITER_BUFFERED
。这是因为没有缓冲,内部循环始终是最内部迭代维的大小,并且允许将其截断将需要特殊处理,从而使其更像是缓冲版本。
NPY_ITER_BUFFERED
¶使迭代器存储缓冲数据,并使用缓冲来满足数据类型,对齐方式和字节顺序要求。要缓冲操作数,请不要指定
NPY_ITER_COPY
或NPY_ITER_UPDATEIFCOPY
标志,因为它们将覆盖缓冲。缓冲对于使用迭代器的Python代码特别有用,它可以一次允许更大的数据块来摊销Python解释器的开销。如果与一起使用
NPY_ITER_EXTERNAL_LOOP
,则由于步幅的布局方式,调用者的内部循环可能会获得比没有缓冲时可能更大的块。请注意,如果为操作数指定了标志
NPY_ITER_COPY
或NPY_ITER_UPDATEIFCOPY
,则将优先于缓冲区进行复制。广播数组时,缓冲仍将发生,因此需要复制元素以获得恒定的步幅。在普通缓冲中,每个内部循环的大小等于缓冲区大小,如果
NPY_ITER_GROWINNER
指定,则可能更大 。如果NPY_ITER_REDUCE_OK
启用并且发生缩减,则根据缩减的结构,内部环路可能会变小。
NPY_ITER_GROWINNER
¶启用缓冲后,如果不需要缓冲,则允许内部循环的大小增加。如果您要直通所有数据,而不是每个内部循环都使用小型的,友好的临时值数组,则最好使用此选项。
NPY_ITER_DELAY_BUFALLOC
¶启用缓冲后,这会延迟缓冲区的分配,直到
NpyIter_Reset
调用另一个复位功能。存在此标志以避免在为多线程迭代制作缓冲迭代器的多个副本时避免浪费地复制缓冲区数据。此标志的另一种用法是设置缩减操作。创建迭代器并由迭代器自动分配归约输出(确保使用READWRITE访问)后,可以将其值初始化为归约单元。使用
NpyIter_GetOperandArray
来获取对象。然后,调用NpyIter_Reset
分配并填充缓冲区的初始值。
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_READWRITE
或NPY_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]
这些数据以本机字节顺序,根据dtype要求对齐,连续或任意组合。默认情况下,迭代器将指针生成到所提供的数组中,这些数组可以是对齐的或不对齐的,并且具有任何字节顺序。如果未启用复制或缓冲并且操作数数据不满足约束条件,将引发错误。
连续约束仅适用于内部循环,连续的内部循环可能具有任意指针更改。
如果请求的数据类型为非本机字节顺序,则NBO标志将覆盖它,并且请求的数据类型将转换为本机字节顺序。
NPY_ITER_ALLOCATE
¶这是用于输出数组的,要求 设置标志
NPY_ITER_WRITEONLY
或NPY_ITER_READWRITE
。如果op[i]
为NULL,则创建一个具有最终广播尺寸的新数组,以及一个与迭代器的迭代顺序匹配的布局。当
op[i]
为NULL时,请求的数据类型op_dtypes[i]
也可以为NULL,在这种情况下,它是根据标记为可读的数组的dtypes自动生成的。生成dtype的规则与UFunc相同。特别要注意的是在选定的dtype中处理字节顺序。如果只有一个输入,则按原样使用输入的dtype。否则,如果将多个输入dtypes组合在一起,则输出将以本机字节顺序。在分配了此标志后,调用方可以通过调用
NpyIter_GetOperandArray
并获取返回的C数组中的第i个对象来检索新数组。调用者必须在其上调用Py_INCREF才能声明对该数组的引用。
NPY_ITER_NO_SUBTYPE
¶与配合使用时
NPY_ITER_ALLOCATE
,此标志将禁用为输出分配数组子类型,从而将其强制为纯正ndarray。TODO:也许最好引入一个功能
NpyIter_GetWrappedOutput
并删除该标志?
NPY_ITER_NO_BROADCAST
¶确保输入或输出与迭代尺寸完全匹配。
NPY_ITER_ARRAYMASK
¶1.7版中的新功能。
指示此操作数是在写入已
NPY_ITER_WRITEMASKED
应用标志的操作数时用于选择元素的掩码。只有一个操作数可以NPY_ITER_ARRAYMASK
应用标志。用该标志的操作数的数据类型应该是
NPY_BOOL
,NPY_MASK
,或者一个struct D型细胞,其字段都是有效的掩模dtypes。在后一种情况下,它必须与结构操作数WRITEMASKED相匹配,因为它正在为该数组的每个字段指定一个掩码。该标志仅影响从缓冲区写回阵列的操作。这意味着,如果操作数也是
NPY_ITER_READWRITE
或NPY_ITER_WRITEONLY
,则执行迭代的代码可以写入该操作数,以控制哪些元素将保持不变,哪些元素将被修改。当掩码应为输入掩码的组合时,这很有用。
NPY_ITER_WRITEMASKED
¶1.7版中的新功能。
该数组是所有
writemasked
操作数的掩码。代码使用writemasked
标志,该标志指示仅将所选ARRAYMASK操作数为True的元素写入。通常,迭代器不会强制执行此操作,而是由代码执行迭代来遵循该承诺。当使用
writemasked
flag并缓冲此操作数时,这将更改如何将数据从缓冲区复制到数组中。使用屏蔽的复制例程,该例程仅复制缓冲区中writemasked
的元素,该数组从ARRAYMASK操作数中的相应元素返回true。
NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE
¶在内存重叠检查中,假定已
NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE
启用的操作数 仅按迭代器顺序进行访问。这使迭代器可以推理出数据依赖性,从而可以避免不必要的复制。
该标志仅
NPY_ITER_COPY_IF_OVERLAP
在迭代器上启用时才有效。
NpyIter_AdvancedNew
(npy_intp NOP,PyArrayObject ** 运算,npy_uint32 标志,NPY_ORDER 秩序,NPY_CASTING 铸造,npy_uint32 * op_flags,PyArray_Descr ** op_dtypes,INT oa_ndim,诠释** op_axes,npy_intp常量* itershape,npy_intp 缓冲区大小)¶扩展NpyIter_MultiNew
了几个高级选项,可以更好地控制广播和缓冲。
如果-1 / NULL值传递给oa_ndim
,op_axes
,itershape
,和buffersize
,就相当于NpyIter_MultiNew
。
参数oa_ndim
(不为零或-1时)指定将使用自定义广播迭代的尺寸数。如果提供,则op_axes
必须并且itershape
也可以提供。该op_axes
参数使您可以详细控制操作数数组的轴如何相互匹配和迭代。在中op_axes
,必须提供一个nop
指向oa_ndim
类型为size 的数组的指针数组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之前并没有使用。已过时,应将其替换为-1。通过用于这种情况,可以实现更好的向后兼容性。oa_ndim == 0` was used for signalling that
that ``op_axes
itershape
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,否则返回分配的迭代器。