数组接口协议#
注意
此页面描述了 NumPy 特定的 API,用于从其他 C 扩展访问 NumPy 数组的内容。PEP 3118 — 修订的缓冲区协议 引入了类似的标准 API,供任何扩展模块使用。Cython 的缓冲区数组支持使用 PEP 3118 API;请参阅 Cython NumPy 教程。
- version:
3
数组接口(有时称为数组协议)创建于 2005 年,作为一种让类数组 Python 对象在可能的情况下智能地重用彼此数据缓冲区的方式。同质 N 维数组接口是对象共享 N 维数组内存和信息的默认机制。该接口由 Python 端和 C 端组成,使用两个属性。希望在应用程序代码中被视为 N 维数组的对象应支持至少一个这些属性。希望支持 N 维数组在应用程序代码中的对象应查找至少一个这些属性,并适当地使用提供的信息。
此接口描述了同质数组,因为它意味着数组的每个项都具有相同的“类型”。这种类型可以非常简单,也可以是相当任意和复杂的类 C 结构。
有两种使用接口的方法:Python 端和 C 端。两者都是独立的属性。
Python 端#
此接口方法包括对象具有 __array_interface__ 属性。
- object.__array_interface__#
一个项目字典(3 个必需项和 5 个可选项)。如果未提供可选键,则表示它们具有隐含的默认值。
键为
- shape(必需)
一个元组,其元素表示数组在每个维度的大小。每个条目都是一个整数(Python
int)。请注意,这些整数可能大于平台int或long可以容纳的大小(Pythonint是 Clong)。使用此属性的代码应适当地处理此情况;可以通过在可能发生溢出时引发错误,或在 C 中使用long long作为形状的 C 类型来处理。- typestr(必需)
一个字符串,提供同质数组的基本类型。基本字符串格式由 3 部分组成:描述数据字节顺序的字符(
<:小端,>:大端,|:不相关),给出数组基本类型的字符代码,以及一个提供类型使用的字节数的整数。基本类型字符代码为
- descr(可选)
一个元组列表,提供同质数组中每个项的内存布局的更详细描述。列表中的每个元组都有两到三个元素。通常,当 *typestr* 为
V[0-9]+时,会使用此属性,但这并非强制要求。唯一的要求是 *typestr* 键中表示的字节数与此处表示的总字节数相同。其想法是支持构成数组元素的类 C 结构体的描述。列表中每个元组的元素是一个字符串,提供与数据类型此部分关联的名称。这也可以是一个元组
('full name', 'basic_name'),其中 basic name 是一个有效的 Python 变量名,表示字段的完整名称。要么是一个基本类型描述字符串,如 *typestr*,要么是另一个列表(用于嵌套结构化类型)。
一个可选的形状元组,提供此结构部分应重复的次数。如果未给出,则假定不重复。使用此通用接口可以描述非常复杂的结构。请注意,数组的每个元素仍然是相同的数据类型。下面给出了一些使用此接口的示例。
默认:
[('', typestr)]- data
一个 2 元组,其第一个参数是一个指向存储数组内容的 data-area 的 Python 整数。
注意
从 C/C++ 通过
PyLong_From*或 Cython、pybind11 等高级绑定进行转换时,请务必使用足够位数的整数。此指针必须指向数据的第一个元素(换句话说,在这种情况下,任何偏移量都将被忽略)。元组中的第二个条目是一个只读标志(true 表示数据区域是只读的)。
此属性也可以是一个公开 缓冲区接口 的对象,它将用于共享数据。如果此键为
None,则内存共享将通过对象本身的缓冲区接口进行。在这种情况下,偏移量键可用于指示缓冲区的开始。如果内存区域需要保护,则新对象必须存储公开数组接口的对象的一个引用。注意
不指定此字段会使用“标量”路径,我们可能会在将来删除它,因为我们不知道任何用户。在这种情况下,NumPy 将原始对象作为标量分配到数组中。
2.4 版已更改:在 NumPy 2.4 之前,
NULL指针使用了未文档化的“标量”路径,因此通常不被接受(并在某些路径上触发崩溃)。在 NumPy 2.4 之后,NULL被接受,尽管 NumPy 将创建一个 1 字节大小的新分配给数组。- strides(可选)
要幺是
None,表示 C 风格的连续数组,要幺是一个步幅元组,它提供了跳转到对应维度中下一个数组元素所需的字节数。每个条目都必须是一个整数(Pythonint)。与 shape 类似,值可能比 Cint或long能表示的要大;调用代码应适当地处理此情况,要么通过引发错误,要么在 C 中使用long long。默认值为None,表示 C 风格的连续内存缓冲区。在此模型中,数组的最后一个维度变化最快。例如,对于元素长度为 8 字节且 shape 为(10, 20, 30)的对象的默认步幅元组将是(4800, 240, 8)。默认:
None(C 风格连续)- mask(可选)
None或一个公开数组接口的对象。掩码数组的所有元素仅应解释为 true 或 not true,指示此数组的哪些元素有效。此对象的 shape 应 “可广播”到原始数组的 shape。默认:
None(所有数组值均有效)- offset(可选)
数组数据区域的整数偏移量。仅当 data 为
None或返回memoryview对象时才能使用此参数。默认:
0。- version(必需)
一个显示接口版本的整数(即此版本的 3)。请注意不要使用此版本来使公开将来版本接口的对象失效。
C 结构访问#
此接口方法允许仅通过一次属性查找和定义明确的 C 结构来更快地访问数组。
- object.__array_struct__#
一个
PyCapsule,其pointer成员包含指向已填充的PyArrayInterface结构的指针。该结构的内存是动态创建的,并且PyCapsule也是用适当的析构函数创建的,因此此属性的检索者只需在完成后对返回的对象调用Py_DECREF。此外,需要将数据复制出去,或者必须持有公开此属性的对象的引用以确保数据不会被释放。公开__array_struct__接口的对象,在其他对象引用它们时,也不能重新分配其内存。
结构 PyArrayInterface 在 numpy/ndarrayobject.h 中定义为
typedef struct {
int two; /* contains the integer 2 -- simple sanity check */
int nd; /* number of dimensions */
char typekind; /* kind in array --- character code of typestr */
int itemsize; /* size of each element */
int flags; /* flags indicating how the data should be interpreted */
/* must set ARR_HAS_DESCR bit to validate descr */
Py_ssize_t *shape; /* A length-nd array of shape information */
Py_ssize_t *strides; /* A length-nd array of stride information */
void *data; /* A pointer to the first element of the array */
PyObject *descr; /* NULL or data-description (same as descr key
of __array_interface__) -- must set ARR_HAS_DESCR
flag or this will be ignored. */
} PyArrayInterface;
flags 成员可能包含 5 位,指示数据应如何解释,以及 1 位指示 Interface 应如何解释。数据位是 NPY_ARRAY_C_CONTIGUOUS (0x1)、NPY_ARRAY_F_CONTIGUOUS (0x2)、NPY_ARRAY_ALIGNED (0x100)、NPY_ARRAY_NOTSWAPPED (0x200) 和 NPY_ARRAY_WRITEABLE (0x400)。最后一个标志 NPY_ARR_HAS_DESCR (0x800) 指示此结构是否具有 arrdescr 字段。除非存在此标志,否则不应访问该字段。
-
NPY_ARR_HAS_DESCR#
2006 年 6 月 16 日新增
过去,大多数实现使用 PyCObject(现在是 PyCapsule)的 desc 成员本身(不要将其与上面 PyArrayInterface 结构中的“descr”成员混淆 — 它们是两个独立的东西)来保存指向公开接口的对象的指针。这现在是接口的显式部分。请务必获取该对象的引用,并在返回 PyCapsule 之前调用 PyCapsule_SetContext,并配置一个析构函数来 decref 此引用。
注意
__array_struct__ 被认为是遗留的,不应用于新代码。请改用 缓冲区协议 或 DLPack 协议 numpy.from_dlpack。
类型描述示例#
为清晰起见,提供一些类型描述和相应的 __array_interface__ ‘descr’ 条目的示例很有用。感谢 Scott Gilbert 提供这些示例。
在所有情况下,“descr”键都是可选的,但当然提供了更多可能对各种应用程序重要的信息。
* Float data
typestr == '>f4'
descr == [('','>f4')]
* Complex double
typestr == '>c8'
descr == [('real','>f4'), ('imag','>f4')]
* RGB Pixel data
typestr == '|V3'
descr == [('r','|u1'), ('g','|u1'), ('b','|u1')]
* Mixed endian (weird but could happen).
typestr == '|V8' (or '>u8')
descr == [('big','>i4'), ('little','<i4')]
* Nested structure
struct {
int ival;
struct {
unsigned short sval;
unsigned char bval;
unsigned char cval;
} sub;
}
typestr == '|V8' (or '<u8' if you want)
descr == [('ival','<i4'), ('sub', [('sval','<u2'), ('bval','|u1'), ('cval','|u1') ]) ]
* Nested array
struct {
int ival;
double data[16*4];
}
typestr == '|V516'
descr == [('ival','>i4'), ('data','>f8',(16,4))]
* Padded structure
struct {
int ival;
double dval;
}
typestr == '|V16'
descr == [('ival','>i4'),('','|V4'),('dval','>f8')]
应该清楚,任何结构化类型都可以使用此接口进行描述。
与数组接口(版本 2)的区别#
版本 2 接口非常相似。差异主要在于外观。尤其地
PyArrayInterface 结构在末尾没有 descr 成员(因此没有 ARR_HAS_DESCR 标志)。
从
__array_struct__返回的PyCapsule的context成员(以前是PyCObject的desc成员)未被指定。通常,它是公开数组的对象(这样可以保留其引用,并在 C 对象销毁时销毁)。现在明确要求该字段以某种方式用于持有对拥有对象的引用。注意
直到 2020 年 8 月,这里写着:
现在它必须是一个元组,其第一个元素是一个字符串,内容为“PyArrayInterface Version #”,第二个元素是公开数组的对象。
此设计在提出后几乎立即被撤回,请参阅 <https://mail.python.org/pipermail/numpy-discussion/2006-June/020995.html>。尽管有 14 年的文档与之相反,但
__array_interface__胶囊在任何时候都未曾包含此元组内容。从
__array_interface__['data']返回的元组以前是十六进制字符串(现在是整数或长整数)。没有
__array_interface__属性,而是__array_interface__字典中的所有键(版本除外)都是它们自己的属性:因此,要获取 Python 端的信息,您必须单独访问以下属性:__array_data____array_shape____array_strides____array_typestr____array_descr____array_offset____array_mask__