数组接口协议#

备注

本页描述了从其他C扩展访问NumPy数组内容的NumPy特定API.:pep:3118修订后的缓冲协议 为Python 2.6和3.0引入了类似的、标准化的API,供任何扩展模块使用.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 (required)

其元素是每个维度中数组大小的元组.每个条目是一个整数(Python int).请注意,这些整数可能大于平台 intlong 所能容纳的大小(Python int 是 C long).使用此属性的代码需要适当处理这个问题;要么在可能溢出时引发错误,要么使用 long long 作为形状的 C 类型.

typestr (required)

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

基本类型字符代码是:

t

位域(后面的整数表示位域中的位数).

b

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

i

整数

u

无符号整数

f

浮点数

c

复杂浮点数

m

Timedelta

M

Datetime

O

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

S

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

U

Unicode(固定长度的 Py_UCS4 序列)

V

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

descr (optional)

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

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

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

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

Default: [('', typestr)]

data (optional)

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

备注

当通过 PyLong_From* 或如 Cython 或 pybind11 这样的高级绑定从 C/C++ 转换时,确保使用足够大位数的整数.

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

这个属性也可以是一个暴露 缓冲接口 的对象,该对象将被用来共享数据.如果这个键不存在(或返回 None),那么将通过对象本身的缓冲接口进行内存共享.在这种情况下,可以使用偏移键来指示缓冲区的起始位置.如果需要确保内存区域的安全,必须存储暴露数组接口的对象的引用.

Default: None

strides (optional)

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

默认:None (C-style 连续)

mask (optional)

None 或一个暴露数组接口的对象.掩码数组的所有元素应仅解释为真或非真,指示此数组的哪些元素是有效的.此对象的形状应为 “可广播” 到原始数组的形状.

默认: None (所有数组值都是有效的)

offset (optional)

数组数据区域中的一个整数偏移量.这只能在数据为 None 或返回一个 memoryview 对象时使用.

Default: 0.

version (required)

一个显示接口版本的整数(例如,此版本为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``(现在是 :c:type:`PyCapsule`)本身的 ``desc 成员(不要将这与上面 PyArrayInterface 结构的”descr”成员混淆——它们是两件不同的事情)来保存指向暴露接口的对象的指针.这现在是接口的一个显式部分.确保在返回 PyCapsule 之前获取对象的引用并调用 PyCapsule_SetContext,并配置一个析构函数来减少此引用的引用计数.

备注

__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__