数组接口协议#
注意
此页面描述了 NumPy 特定的 API,用于从其他 C 扩展访问 NumPy 数组的内容。PEP 3118 – The Revised Buffer Protocol
为任何扩展模块引入了类似的、标准化的 API 用于 Python 2.6 和 3.0。Cython 的缓冲区数组支持使用PEP 3118 API;参见Cython NumPy 教程。Cython 提供了一种编写代码的方法,该代码支持 Python 2.6 之前的版本的缓冲区协议,因为它具有一个利用此处描述的数组接口的向后兼容的实现。
- 版本:
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
)。使用此属性的代码应该适当地处理此问题;在可能发生溢出时引发错误,或者使用long long
作为形状的 C 类型。- typestr (必需)
一个字符串,提供同构数组的基本类型。基本字符串格式由 3 部分组成:一个字符描述数据的字节序(
<
:小端,>
:大端,|
:不相关),一个字符代码给出数组的基本类型,以及一个整数,提供类型使用的字节数。基本类型字符代码为
- descr (可选)
一个元组列表,提供对同构数组中每个项目的内存布局的更详细描述。列表中的每个元组都有两个或三个元素。通常,当 typestr 为
V[0-9]+
时,将使用此属性,但这并非必需。唯一的要求是 typestr 键中表示的字节数与此处表示的总字节数相同。其目的是支持构成数组元素的类 C 结构的描述。列表中每个元组的元素为一个字符串,提供与数据类型这部分关联的名称。这也可以是一个
('full name', 'basic_name')
元组,其中基本名称将是一个有效的 Python 变量名,代表字段的全名。在 typestr 中的另一个基本类型描述字符串或另一个列表(用于嵌套结构化类型)
一个可选的形状元组,提供结构的这部分应该重复多少次。如果没有给出,则假定没有重复。可以使用此通用接口描述非常复杂的结构。但是请注意,数组的每个元素仍然是相同的数据类型。下面给出了一些使用此接口的示例。
默认值:
[('', typestr)]
- data (可选)
一个 2 元组,其第一个参数是指向存储数组内容的数据区域的 Python 整数。
注意
当通过
PyLong_From*
或高级绑定(例如 Cython 或 pybind11)从 C/C++ 转换时,请确保使用足够大的位数的整数。此指针必须指向数据的第一个元素(换句话说,在这种情况下始终忽略任何偏移量)。元组中的第二个条目是只读标志(true 表示数据区域为只读)。
此属性也可以是一个公开 缓冲区接口 的对象,这将用于共享数据。如果此键不存在(或返回 None),则将通过对象本身的缓冲区接口进行内存共享。在这种情况下,可以使用 offset 键来指示缓冲区的起始位置。如果要保护内存区域,则新对象必须存储公开数组接口的对象的引用。
默认值:
None
- strides (可选)
或者是
None
,指示 C 样式的连续数组,或者是一个步幅元组,它提供跳转到相应维度中的下一个数组元素所需的字节数。每个条目必须是一个整数(Pythonint
)。与 shape 一样,这些值可能大于 Cint
或long
可以表示的值;调用代码应该适当地处理此问题,或者通过引发错误,或者在 C 中使用long long
。默认值为None
,这意味着 C 样式的连续内存缓冲区。在此模型中,数组的最后一个维度变化最快。例如,对于数组条目长度为 8 字节且形状为(10, 20, 30)
的对象的默认步幅元组将为(4800, 240, 8)
。默认值:
None
(C 样式连续)- mask (可选)
None
或一个公开数组接口的对象。掩码数组的所有元素都应该仅解释为真或非真,指示此数组的哪些元素有效。此对象的形状应该与原始数组的形状“广播”兼容。默认值:
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 位表示如何解释接口。数据位是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_SetContext
,然后再返回PyCapsule
,并配置一个析构函数来递减此引用。
注意
__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 版本 #”的字符串,第二个元素是公开数组的对象。
这个设计在提出后几乎立即被撤回,详情参见<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__