数组接口协议#

注意

此页面描述了 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)。请注意,这些整数可能大于平台 intlong 可以容纳的大小(Python int 是 C long)。使用此属性的代码应适当地处理此情况;可以通过在可能发生溢出时引发错误,或在 C 中使用 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(可选)

一个元组列表,提供同质数组中每个项的内存布局的更详细描述。列表中的每个元组都有两到三个元素。通常,当 *typestr* 为 V[0-9]+ 时,会使用此属性,但这并非强制要求。唯一的要求是 *typestr* 键中表示的字节数与此处表示的总字节数相同。其想法是支持构成数组元素的类 C 结构体的描述。列表中每个元组的元素是

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

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

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

默认[('', 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 风格的连续数组,要幺是一个步幅元组,它提供了跳转到对应维度中下一个数组元素所需的字节数。每个条目都必须是一个整数(Python int)。与 shape 类似,值可能比 C intlong 能表示的要大;调用代码应适当地处理此情况,要么通过引发错误,要么在 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__ 接口的对象,在其他对象引用它们时,也不能重新分配其内存。

结构 PyArrayInterfacenumpy/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 接口非常相似。差异主要在于外观。尤其地

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

  2. __array_struct__ 返回的 PyCapsulecontext 成员(以前是 PyCObjectdesc 成员)未被指定。通常,它是公开数组的对象(这样可以保留其引用,并在 C 对象销毁时销毁)。现在明确要求该字段以某种方式用于持有对拥有对象的引用。

    注意

    直到 2020 年 8 月,这里写着:

    现在它必须是一个元组,其第一个元素是一个字符串,内容为“PyArrayInterface Version #”,第二个元素是公开数组的对象。

    此设计在提出后几乎立即被撤回,请参阅 <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__