数组接口协议#

注意

本页面介绍了从其他 C 扩展访问 NumPy 数组内容的 NumPy 特定 API。 PEP 3118The Revised Buffer Protocol 为任何扩展模块引入类似的标准化 API,适用于 Python 2.6 和 3.0。 Cython 的缓冲区数组支持使用 PEP 3118 API;请参阅 Cython NumPy 教程。 Cython 提供了一种方法,可以使用低于 2.6 的 Python 版本编写支持缓冲区协议的代码,因为它具有利用此处描述的数组接口的向后兼容实现。

版本:

3

数组接口(有时称为数组协议)于 2005 年创建,旨在使类似数组的 Python 对象能够在可能的情况下智能地重用彼此的数据缓冲区。同质 N 维数组接口是对象共享 N 维数组内存和信息的默认机制。该接口包括使用两个属性的 Python 端和 C 端。希望在应用程序代码中被视为 N 维数组的对象应至少支持这些属性之一。希望在应用程序代码中支持 N 维数组的对象应至少查找这些属性之一,并适当地使用提供的信息。

此接口描述了同质数组,因为数组的每个项目都具有相同的“类型”。这种类型可以非常简单,也可以是相当任意且复杂的 C 风格结构。

有两种使用该接口的方法:Python 端和 C 端。两者都是独立的属性。

Python 端#

这种接口方法包括该对象具有 __array_interface__ 属性。

object.__array_interface__#

一个包含项目(3 个必需,5 个可选)的字典。如果字典中没有提供可选键,则它们具有隐含的默认值。

键是

shape (必需)

一个元组,其元素是每个维度上的数组大小。每个条目都是一个整数(Python int)。请注意,这些整数可能大于平台 intlong 可以容纳的范围(Python int 是 C long)。使用此属性的代码应适当地处理这种情况;如果可能发生溢出,则引发错误,或者将 long long 用作 C 类型以表示形状。

typestr (必需)

提供同质数组的基本类型的字符串。基本字符串格式包括 3 部分:一个字符描述数据的字节顺序(<:小端,>:大端,|:不相关)、一个字符代码给出数组的基本类型,以及一个整数提供类型使用的字节数。

基本类型字符代码是

t

位域(后面的整数给出位域中的位数)。

b

布尔值(整数类型,其中所有值仅为 TrueFalse

i

整数

u

无符号整数

f

浮点数

c

复数浮点数

m

时间增量

M

日期时间

O

对象(即内存包含指向 PyObject 的指针)

S

字符串(固定长度的 char 序列)

U

Unicode(固定长度的 Py_UCS4 序列)

V

其他(void * – 每个项目都是一个固定大小的内存块)

descr (可选)

一个元组列表,提供对同质数组中每个项目内存布局的更详细描述。列表中的每个元组都有两个或三个元素。通常,当 typestrV[0-9]+ 时,将使用此属性,但这并不是必需的。唯一的要求是 typestr 键中表示的字节数与此处表示的总字节数相同。这样做的目的是支持构成数组元素的 C 风格结构的描述。列表中每个元组的元素是

  1. 一个字符串,提供与数据类型这部分关联的名称。它也可以是 ('full name', 'basic_name') 的元组,其中基本名称将是表示字段全名的有效 Python 变量名称。

  2. 要么是 typestr 中的基本类型描述字符串,要么是另一个列表(用于嵌套结构化类型)。

  3. 一个可选的形状元组,提供结构这部分应该重复的次数。如果没有给出,则假定没有重复。可以使用此通用接口来描述非常复杂的结构。但是请注意,数组的每个元素仍然具有相同的数据类型。以下是一些使用此接口的示例。

默认值[('', typestr)]

data (可选)

一个 2 元组,其第一个参数是一个 Python 整数,指向存储数组内容的数据区域。

注意

通过 PyLong_From* 或高级绑定(如 Cython 或 pybind11)从 C/C++ 转换时,请确保使用具有足够大位宽的整数。

此指针必须指向数据的第一个元素(换句话说,在这种情况下始终忽略任何偏移量)。元组中的第二个条目是一个只读标志(true 表示数据区域是只读的)。

此属性也可以是一个公开 缓冲区接口 的对象,该接口将用于共享数据。如果此键不存在(或返回 None),则将通过对象本身的缓冲区接口进行内存共享。在这种情况下,可以使用 offset 键来指示缓冲区的起始位置。如果要保护内存区域,则新对象必须存储对公开数组接口的对象的引用。

默认值None

strides (可选)

要么是 None,表示 C 风格的连续数组,要么是步长元组,提供跳转到相应维度上的下一个数组元素所需的字节数。每个条目必须是一个整数(Python int)。与形状一样,值可能大于 C intlong 可以表示的范围;调用代码应适当地处理这种情况,要么引发错误,要么在 C 中使用 long long。默认值为 None,这意味着 C 风格的连续内存缓冲区。在此模型中,数组的最后一个维度变化最快。例如,对于其数组条目长度为 8 字节且形状为 (10, 20, 30) 的对象的默认步长元组将是 (4800, 240, 8)

默认值None(C 风格的连续)

mask (可选)

None 或一个公开数组接口的对象。掩码数组的所有元素都应仅解释为 true 或非 true,指示该数组的哪些元素有效。此对象的形状应与原始数组的形状“可广播”

默认值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 日起新增

过去,大多数实现使用 desc 成员,该成员属于 PyCObject(现在为 PyCapsule)本身(不要将此与上述 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 接口非常相似。差异主要是审美上的。特别是

  1. PyArrayInterface 结构在末尾没有 descr 成员(因此没有 ARR_HAS_DESCR 标志)

  2. __array_struct__ 返回的 PyCapsulecontext 成员(以前是 PyCObjectdesc 成员)未指定。通常,它是暴露数组的对象(以便可以保留对它的引用并在 C 对象被销毁时销毁它)。现在有一个明确的要求,即此字段必须以某种方式用于保存对拥有对象的引用。

    注意

    在 2020 年 8 月之前,它说的是

    现在它必须是一个元组,其第一个元素是一个字符串,包含“PyArrayInterface 版本 #”,其第二个元素是暴露数组的对象。

    这种设计几乎是在提出后立即被撤回的,在 <https://mail.python.org/pipermail/numpy-discussion/2006-June/020995.html> 中。尽管有 14 年的文档记录与之相反,但在任何时候,假设 __array_interface__ 胶囊包含此元组内容都是无效的。

  3. __array_interface__['data'] 返回的元组曾经是一个十六进制字符串(现在是一个整数或一个长整数)。

  4. 没有 __array_interface__ 属性,而是 __array_interface__ 字典中的所有键(除了版本)都是它们自己的属性:因此,要获得 Python 端信息,您必须分别访问这些属性

    • __array_data__

    • __array_shape__

    • __array_strides__

    • __array_typestr__

    • __array_descr__

    • __array_offset__

    • __array_mask__