Python 类型和 C 结构体#

C 代码中定义了几种新类型。其中大多数可通过 Python 访问,但少数由于其用途有限而未暴露。每个新的 Python 类型都有一个关联的 PyObject*,其内部结构包含一个指向“方法表”的指针,该表定义了新对象在 Python 中的行为方式。当您在 C 代码中接收到一个 Python 对象时,您总是会得到一个指向 PyObject 结构的指针。由于 PyObject 结构非常通用,并且只定义了 PyObject_HEAD,因此它本身并不是很有趣。但是,不同的对象在 PyObject_HEAD 之后包含更多细节(但您必须将其转换为正确的类型才能访问它们——或者使用访问器函数或宏)。

新定义的 Python 类型#

Python 类型在 C 中是 Python 中类的功能等价物。通过构造一个新的 Python 类型,您可以为 Python 提供一个新对象。ndarray 对象是在 C 中定义的新类型的一个示例。新类型在 C 中通过两个基本步骤定义:

  1. 创建一个与 PyObject 结构本身二进制兼容的 C 结构体(通常命名为 Py{Name}Object),但它包含该特定对象所需的额外信息;

  2. 用指向实现该类型所需行为的函数的指针填充 PyTypeObject 表(由 PyObject 结构的 ob_type 成员指向)。

与定义 Python 类行为的特殊方法名称不同,这里有指向实现所需结果的函数的“函数表”。PyTypeObject 本身是动态的,这允许 C 类型在 C 中从其他 C 类型“子类型化”,并在 Python 中子类化。子类型继承其父类型的属性和方法。

有两种主要的全新类型:ndarray (PyArray_Type) 和 ufunc (PyUFunc_Type)。其他类型则扮演辅助角色:PyArrayIter_TypePyArrayMultiIter_TypePyArrayDescr_TypePyArrayIter_Type 是 ndarray 的扁平迭代器类型(当获取 flat 属性时返回的对象)。PyArrayMultiIter_Type 是调用 broadcast 时返回的对象的类型。它处理对嵌套序列集合的迭代和广播。此外,PyArrayDescr_Type 是数据类型描述符类型,其实例描述了数据,而 PyArray_DTypeMeta 是数据类型描述符的元类。还有新的标量数组类型,它们是与数组可用的每个基本数据类型对应的新的 Python 标量。其他类型是占位符,允许数组标量适应实际 Python 类型的层次结构。最后,与 NumPy 内置数据类型对应的 PyArray_DTypeMeta 实例也公开可见。

PyArray_Type 和 PyArrayObject#

PyTypeObject PyArray_Type#

ndarray 的 Python 类型是 PyArray_Type。在 C 中,每个 ndarray 都是一个指向 PyArrayObject 结构的指针。此结构的 ob_type 成员包含一个指向 PyArray_Type 类型对象的指针。

type PyArrayObject#
type NPY_AO#

PyArrayObject C 结构体包含数组所需的所有信息。ndarray(及其子类)的所有实例都将具有此结构。为了未来的兼容性,这些结构成员通常应使用提供的宏访问。如果您需要一个较短的名称,那么您可以使用 NPY_AO(已废弃),它定义为与 PyArrayObject 等价。直接访问结构字段已废弃。请改用 PyArray_*(arr) 形式。从 NumPy 1.20 开始,此结构的大小不被视为 NumPy ABI 的一部分(请参阅成员列表末尾的注释)。

typedef struct PyArrayObject {
    PyObject_HEAD
    char *data;
    int nd;
    npy_intp *dimensions;
    npy_intp *strides;
    PyObject *base;
    PyArray_Descr *descr;
    int flags;
    PyObject *weakreflist;
    /* version dependent private members */
} PyArrayObject;
PyObject_HEAD

所有 Python 对象都需要此项。它至少包含一个引用计数成员 (ob_refcnt) 和一个指向类型对象 (ob_type) 的指针。(如果 Python 是用特殊选项编译的,可能还会存在其他元素,详情请参阅 Python 源代码树中的 Include/object.h)。ob_type 成员指向一个 Python 类型对象。

char *data#

可通过 PyArray_DATA 访问,此数据成员是指向数组第一个元素的指针。此指针可以(并且通常应该)被重新转换为数组的数据类型。

int nd#

一个整数,提供此数组的维度数量。当 nd 为 0 时,数组有时被称为秩为 0 的数组。此类数组具有未定义的维度和步长,无法访问。ndarraytypes.h 中定义的宏 PyArray_NDIM 指向此数据成员。NPY_MAXDIMS 定义为编译时常量,限制了维度数量。自 NumPy 2 以来,此数字为 64,之前为 32。但是,我们将来可能希望取消此限制,因此最好显式检查维度以适用于依赖此上限的代码。

npy_intp *dimensions#

一个整数数组,只要 nd \(\geq\) 1,就提供每个维度中的形状。该整数总是足够大以容纳平台上的指针,因此维度大小仅受内存限制。PyArray_DIMS 是与此数据成员关联的宏。

npy_intp *strides#

一个整数数组,为每个维度提供跳过以到达该维度中下一个元素所需的字节数。与宏 PyArray_STRIDES 相关联。

PyObject *base#

PyArray_BASE 指向,此成员用于保存指向与此数组相关的另一个 Python 对象的指针。有两种用例:

  • 如果此数组不拥有自己的内存,则 base 指向拥有它的 Python 对象(可能是另一个数组对象)

  • 如果此数组设置了 NPY_ARRAY_WRITEBACKIFCOPY 标志,则此数组是“行为异常”数组的工作副本。

当调用 PyArray_ResolveWritebackIfCopy 时,base 指向的数组将使用此数组的内容进行更新。

PyArray_Descr *descr#

指向数据类型描述符对象(见下文)的指针。数据类型描述符对象是一种新内置类型的实例,它允许对内存进行通用描述。每种支持的数据类型都有一个描述符结构。此描述符结构包含有关该类型的有用信息以及指向函数指针表的指针,以实现特定功能。顾名思义,它与宏 PyArray_DESCR 相关联。

int flags#

由宏 PyArray_FLAGS 指向,此数据成员表示指示如何解释由数据指向的内存的标志。可能的标志有 NPY_ARRAY_C_CONTIGUOUSNPY_ARRAY_F_CONTIGUOUSNPY_ARRAY_OWNDATANPY_ARRAY_ALIGNEDNPY_ARRAY_WRITEABLENPY_ARRAY_WRITEBACKIFCOPY

PyObject *weakreflist#

此成员允许数组对象具有弱引用(使用 weakref 模块)。

注意

其他成员被认为是私有的且与版本相关。如果结构体的大小对您的代码很重要,则必须特别小心。当在 C 中进行子类化时,这可能是一个相关的用例。如果您的代码依赖于 sizeof(PyArrayObject) 保持不变,则必须在导入时添加以下检查:

if (sizeof(PyArrayObject) < PyArray_Type.tp_basicsize) {
    PyErr_SetString(PyExc_ImportError,
       "Binary incompatibility with NumPy, must recompile/update X.");
    return NULL;
}

为确保您的代码无需为特定的 NumPy 版本编译,您可以添加一个常量,为 NumPy 的未来更改留出空间。与任何未来 NumPy 版本兼容的解决方案需要使用运行时计算的偏移量和分配大小。

PyArray_Type 类型对象实现了 Python objects 的许多特性,包括 tp_as_numbertp_as_sequencetp_as_mappingtp_as_buffer 接口。富比较(rich comparison)也与新式属性查找一起用于成员(tp_members)和属性(tp_getset)。PyArray_Type 也可以子类型化。

提示

tp_as_number 方法使用一种通用方法来调用为处理操作而注册的任何函数。当导入 _multiarray_umath 模块时,它将所有数组的数字操作设置为相应的 ufunc。此选择可以通过 PyUFunc_ReplaceLoopBySignature 更改。

PyGenericArrType_Type#

PyTypeObject PyGenericArrType_Type#

PyGenericArrType_Type 是创建 numpy.generic python 类型的 PyTypeObject 定义。

PyArrayDescr_Type 和 PyArray_Descr#

PyTypeObject PyArrayDescr_Type#

PyArrayDescr_Type 是数据类型描述符对象的内置类型,用于描述组成数组的字节如何解释。对于内置数据类型,有 21 个静态定义的 PyArray_Descr 对象。虽然它们参与引用计数,但它们的引用计数不应达到零。还有一个动态表的用户定义 PyArray_Descr 对象也得到维护。一旦数据类型描述符对象被“注册”,它也永远不应该被释放。函数 PyArray_DescrFromType (…) 可以用于从枚举类型编号(内置或用户定义)检索 PyArray_Descr 对象。

type PyArray_DescrProto#

PyArray_Descr 相同的结构。此结构用于静态定义原型,以便通过 PyArray_RegisterDataType 注册新的旧版 DType。

详情请参阅 PyArray_RegisterDataType 中的注释。

type PyArray_Descr#

PyArray_Descr 结构是 PyArrayDescr_Type 的核心。虽然这里是为了完整性而描述,但它应被视为 NumPy 内部,并通过 PyArrayDescr_*PyDataType* 函数和宏进行操作。此结构的大小在 NumPy 不同版本之间可能会发生变化。为确保兼容性:

  • 永远不要声明非指针的结构实例

  • 永远不要进行指针算术

  • 永远不要使用 sizeof(PyArray_Descr)

其结构如下:

typedef struct {
    PyObject_HEAD
    PyTypeObject *typeobj;
    char kind;
    char type;
    char byteorder;
    char _former_flags;  // unused field
    int type_num;
    /*
     * Definitions after this one must be accessed through accessor
     * functions (see below) when compiling with NumPy 1.x support.
     */
    npy_uint64 flags;
    npy_intp elsize;
    npy_intp alignment;
    NpyAuxData *c_metadata;
    npy_hash_t hash;
    void *reserved_null[2];  // unused field, must be NULLed.
} PyArray_Descr;

某些 dtype 具有额外的成员,可通过 PyDataType_NAMESPyDataType_FIELDSPyDataType_SUBARRAY 以及在某些情况下(时间)PyDataType_C_METADATA 访问。

PyTypeObject *typeobj#

指向 typeobject 的指针,该 typeobject 是此数组元素对应的 Python 类型。对于内置类型,这指向相应的数组标量。对于用户定义类型,这应该指向一个用户定义 typeobject。此 typeobject 可以继承自数组标量,也可以不继承。如果它不继承自数组标量,则应在 flags 成员中设置 NPY_USE_GETITEMNPY_USE_SETITEM 标志。

char kind#

一个字符代码,指示数组的类型(使用数组接口类型字符串表示法)。'b' 表示布尔,'i' 表示有符号整数,'u' 表示无符号整数,'f' 表示浮点数,'c' 表示复浮点数,'S' 表示 8 位以零结尾的字节,'U' 表示 32 位/字符 Unicode 字符串,'V' 表示任意类型。

char type#

指示数据类型的传统字符代码。

char byteorder#

一个字符,指示字节序:“>”(大端序),“<”(小端序),“=”(本机),“|”(不相关,忽略)。所有内置数据类型都具有字节序“=”。

npy_uint64 flags#

一个数据类型位标志,用于确定数据类型是否表现出类似对象数组的行为。此成员中的每个位都是一个标志,其名称如下:

int type_num#

唯一标识数据类型的数字。对于新数据类型,此数字在数据类型注册时分配。

npy_intp elsize#

对于大小始终相同的数据类型(例如 long),此字段保存数据类型的大小。对于不同数组可以具有不同元素大小的灵活数据类型,此字段应为 0。

请参阅 PyDataType_ELSIZEPyDataType_SET_ELSIZE,了解以 NumPy 1.x 兼容方式访问此字段的方法。

npy_intp alignment#

一个数字,提供此数据类型的对齐信息。具体来说,它显示了从 2 元素结构(其第一个元素是 char)的开头,编译器放置此类型项的距离:offsetof(struct {char c; type v;}, v)

请参阅 PyDataType_ALIGNMENT,了解以 NumPy 1.x 兼容方式访问此字段的方法。

PyObject *metadata#

关于此 dtype 的元数据。

NpyAuxData *c_metadata#

特定于该 dtype 的 C 实现的元数据。为 NumPy 1.7.0 添加。

type npy_hash_t#
npy_hash_t *hash#

用于缓存哈希值。

NPY_ITEM_REFCOUNT#

表示此数据类型的项必须进行引用计数(使用 Py_INCREFPy_DECREF)。

NPY_ITEM_HASOBJECT#

NPY_ITEM_REFCOUNT 相同。

NPY_LIST_PICKLE#

指示此数据类型的数组在 pickle 之前必须转换为列表。

NPY_ITEM_IS_POINTER#

指示该项是指向其他数据类型的指针

NPY_NEEDS_INIT#

指示此数据类型的内存在创建时必须初始化(设置为 0)。

NPY_NEEDS_PYAPI#

指示此数据类型在访问期间需要 Python C-API(因此如果需要数组访问,请不要放弃 GIL)。

NPY_USE_GETITEM#

在数组访问时,使用 f->getitem 函数指针而不是标准转换为数组标量。如果您不定义与数据类型一起使用的数组标量,则必须使用。

NPY_USE_SETITEM#

从数组标量创建 0 维数组时,使用 f->setitem 而不是从数组标量进行标准复制。如果您不定义与数据类型一起使用的数组标量,则必须使用。

NPY_FROM_FIELDS#

如果数据类型的任何字段中设置了这些位,则从父数据类型继承的位。目前(NPY_NEEDS_INIT | NPY_LIST_PICKLE | NPY_ITEM_REFCOUNT | NPY_NEEDS_PYAPI)。

NPY_OBJECT_DTYPE_FLAGS#

为对象数据类型设置的位:(NPY_LIST_PICKLE | NPY_USE_GETITEM | NPY_ITEM_IS_POINTER | NPY_ITEM_REFCOUNT | NPY_NEEDS_INIT | NPY_NEEDS_PYAPI)。

int PyDataType_FLAGCHK(PyArray_Descr *dtype, int flags)#

如果数据类型对象设置了所有给定的标志,则返回 true。

int PyDataType_REFCHK(PyArray_Descr *dtype)#

等价于 PyDataType_FLAGCHK (dtype, NPY_ITEM_REFCOUNT)。

PyArray_ArrFuncs#

PyArray_ArrFuncs *PyDataType_GetArrFuncs(PyArray_Descr *dtype)#

获取数据类型的旧版 PyArray_ArrFuncs(不会失败)。

NumPy 版本 2.0 中的新功能: 此函数是在 NumPy 2.0 中以向后兼容和可回溯的方式添加的(请参阅 npy_2_compat.h)。任何以前访问 PyArray_Descr->f 槽位的代码,现在必须使用此函数并将其回溯以与 1.x 编译。(npy_2_compat.h 头文件可为此目的进行内嵌。)

type PyArray_ArrFuncs#

实现内部功能的函数。并非所有这些函数指针都必须为给定类型定义。必需的成员是 nonzerocopyswapcopyswapnsetitemgetitemcast。这些假定为非 NULLNULL 条目将导致程序崩溃。其他函数可以是 NULL,这意味着该数据类型的功能会减少。(此外,如果 nonzero 函数在您注册用户定义数据类型时为 NULL,则会用默认函数填充。)

typedef struct {
    PyArray_VectorUnaryFunc *cast[NPY_NTYPES_LEGACY];
    PyArray_GetItemFunc *getitem;
    PyArray_SetItemFunc *setitem;
    PyArray_CopySwapNFunc *copyswapn;
    PyArray_CopySwapFunc *copyswap;
    PyArray_CompareFunc *compare;
    PyArray_ArgFunc *argmax;
    PyArray_DotFunc *dotfunc;
    PyArray_ScanFunc *scanfunc;
    PyArray_FromStrFunc *fromstr;
    PyArray_NonzeroFunc *nonzero;
    PyArray_FillFunc *fill;
    PyArray_FillWithScalarFunc *fillwithscalar;
    PyArray_SortFunc *sort[NPY_NSORTS];
    PyArray_ArgSortFunc *argsort[NPY_NSORTS];
    PyObject *castdict;
    PyArray_ScalarKindFunc *scalarkind;
    int **cancastscalarkindto;
    int *cancastto;
    void *_unused1;
    void *_unused2;
    void *_unused3;
    PyArray_ArgFunc *argmin;
} PyArray_ArrFuncs;

函数指针的描述中使用了“行为正常段”的概念。行为正常段是指对齐且以本机机器字节序表示数据类型的段。nonzerocopyswapcopyswapngetitemsetitem 函数可以(并且必须)正确处理行为异常的数组。其他函数需要行为正常的内存段。

注意

这些函数在很大程度上是遗留 API,但是,有些仍在被使用。从 NumPy 2.x 开始,它们只能通过 PyDataType_GetArrFuncs 访问(有关详细信息,请参阅该函数)。在使用结构中定义的任何函数之前,您应该检查它是否为 NULL。通常,可以预期 getitemsetitemcopyswapcopyswapn 函数已被定义,但所有函数都预期会被较新的 API 替换。例如,PyArray_Pack 是一个更强大的 setitem 版本,例如,它能够正确处理类型转换。

void cast(void *from, void *to, npy_intp n, void *fromarr, void *toarr)#

一个函数指针数组,用于从当前类型转换为所有其他内置类型。每个函数将 from 指向的连续、对齐且未字节序交换的缓冲区转换为 to 指向的连续、对齐且未字节序交换的缓冲区。要转换的项数由 n 给出,参数 fromarrtoarr 被解释为 PyArrayObject,以便灵活数组获取项大小信息。

PyObject *getitem(void *data, void *arr)#

一个函数指针,它从 data 指向的数组对象 arr 的单个元素返回一个标准 Python 对象。此函数必须能够正确处理“行为异常”(未对齐和/或字节序交换)的数组。

int setitem(PyObject *item, void *data, void *arr)#

一个函数指针,用于将 Python 对象 item 设置到数组 arr 中由 data 指向的位置。此函数处理“行为异常”的数组。如果成功,返回零,否则返回负一(并设置 Python 错误)。

void copyswapn(void *dest, npy_intp dstride, void *src, npy_intp sstride, npy_intp n, int swap, void *arr)#
void copyswap(void *dest, void *src, int swap, void *arr)#

这两个成员都是函数指针,用于将数据从 src 复制到 dest,并在指示时进行 swaparr 的值仅用于灵活(NPY_STRINGNPY_UNICODENPY_VOID)数组(并从 arr->descr->elsize 获取)。第二个函数复制单个值,而第一个函数以提供的步长循环处理 n 个值。这些函数可以处理行为异常的 src 数据。如果 src 为 NULL,则不执行复制。如果 swap 为 0,则不进行字节交换。假定 destsrc 不重叠。如果它们重叠,则首先使用 memmove (…) 后跟 copyswap(n),其中 src 的值为 NULL。

int compare(const void *d1, const void *d2, void *arr)#

一个函数指针,用于比较数组 arr 中由 d1d2 指向的两个元素。此函数要求数组行为正常(对齐且未字节序交换)。如果 *d1 > *d2,返回 1;如果 *d1 == *d2,返回 0;如果 *d1 < *d2,返回 -1。数组对象 arr 用于检索灵活数组的项大小和字段信息。

int argmax(void *data, npy_intp n, npy_intp *max_ind, void *arr)#

一个函数指针,用于检索数组 arr 中从 data 指向的元素开始的 n 个元素中最大元素的索引。此函数要求内存段是连续且行为正常的。返回值始终为 0。最大元素的索引在 max_ind 中返回。

void dotfunc(void *ip1, npy_intp is1, void *ip2, npy_intp is2, void *op, npy_intp n, void *arr)#

一个函数指针,用于将两个 n 长度的序列相乘,然后相加,并将结果放置在 arr 中由 op 指向的元素中。两个序列的起始位置由 ip1ip2 指向。要到达每个序列中的下一个元素,分别需要跳过 is1is2 字节。此函数要求内存行为正常(尽管不一定是连续的)。

int scanfunc(FILE *fd, void *ip, void *arr)#

一个函数指针,用于从文件描述符 fd 中扫描(scanf 样式)一个对应类型的元素到由 ip 指向的数组内存中。假定数组行为正常。最后一个参数 arr 是要扫描到的数组。返回成功分配的接收参数的数量(如果在第一个接收参数分配之前发生匹配失败,则可能为零),或者如果在第一个接收参数分配之前发生输入失败,则返回 EOF。此函数应在不持有 Python GIL 的情况下调用,并且必须为错误报告获取 GIL。

int fromstr(char *str, void *ip, char **endptr, void *arr)#

一个函数指针,用于将 str 指向的字符串转换为对应类型的一个元素,并将其放置在 ip 指向的内存位置。转换完成后,*endptr 指向字符串的其余部分。最后一个参数 arr 是 ip 指向的数组(可变大小数据类型需要)。成功时返回 0,失败时返回 -1。需要行为正常的数组。此函数应在不持有 Python GIL 的情况下调用,并且必须为错误报告获取 GIL。

npy_bool nonzero(void *data, void *arr)#

一个函数指针,如果 data 指向的 arr 的项为非零,则返回 TRUE。此函数可以处理行为异常的数组。

void fill(void *data, npy_intp length, void *arr)#

一个函数指针,用于用数据填充给定长度的连续数组。数组的前两个元素必须已填充。根据这两个值,将计算一个增量,并通过重复添加此计算出的增量来计算从第 3 个项到末尾的值。数据缓冲区必须行为良好。

void fillwithscalar(void *buffer, npy_intp length, void *value, void *arr)#

一个函数指针,用于用给定的 value 标量(其地址已给出)填充给定 length 的连续 buffer。最后一个参数是数组,需要它来获取可变长度数组的项大小。

int sort(void *start, npy_intp length, void *arr)#

指向特定排序算法的函数指针数组。通过键(目前定义了 NPY_QUICKSORTNPY_HEAPSORTNPY_MERGESORT)获取特定的排序算法。这些排序是就地完成的,假定数据是连续且对齐的。

int argsort(void *start, npy_intp *result, npy_intp length, void *arr)#

这是一个指向该数据类型的排序算法函数指针数组。与排序(sort)功能相同的排序算法也可用。产生排序的索引将在 result 中返回(result 必须用包含 0 到 length-1 的索引进行初始化)。

PyObject *castdict#

NULL 或一个字典,其中包含用户定义数据类型的底层类型转换函数。每个函数都封装在一个 PyCapsule* 中,并以数据类型编号作为键。

NPY_SCALARKIND scalarkind(PyArrayObject *arr)#

一个函数,用于确定此类型的标量应如何解释。参数为 NULL 或一个包含数据的 0 维数组(如果需要通过数据确定标量类型)。返回值必须是 NPY_SCALARKIND 类型。

int **cancastscalarkindto#

NULL 或一个包含 NPY_NSCALARKINDS 指针的数组。这些指针中的每一个都应该是 NULL 或指向一个整数数组的指针(以 NPY_NOTYPE 终止),指示该数据类型指定种类的标量可以安全地转换为哪些数据类型(这通常意味着不会丢失精度)。

int *cancastto#

NULL 或一个整数数组(以 NPY_NOTYPE 终止),指示此数据类型可以安全地转换为哪些数据类型(这通常意味着不会丢失精度)。

int argmin(void *data, npy_intp n, npy_intp *min_ind, void *arr)#

指向一个函数的指针,该函数用于从 arr 中从 data 指向的元素开始的 n 个元素中检索最小元素的索引。此函数要求内存段是连续且行为正常的。返回值始终为 0。最小元素的索引在 min_ind 中返回。

PyArrayMethod_Context 和 PyArrayMethod_Spec#

type PyArrayMethodObject_tag#

一个不透明的结构体,用于在 ArrayMethod 循环中表示方法“self”。

type PyArrayMethod_Context#

一个结构体,传递给 ArrayMethod 循环,为循环的运行时使用提供上下文。

typedef struct {
    PyObject *caller;
    struct PyArrayMethodObject_tag *method;
    PyArray_Descr *const *descriptors;
} PyArrayMethod_Context
PyObject *caller#

调用者,通常是调用此循环的 ufunc。当调用不是来自 ufunc(例如类型转换)时,可能为 NULL

struct PyArrayMethodObject_tag *method#

方法“self”。目前此对象是一个不透明指针。

PyArray_Descr **descriptors#

ufunc 循环的描述符数组,由 resolve_descriptors 填充。数组的长度是 nin + nout

type PyArrayMethod_Spec#

一个结构体,用于向 NumPy 注册 ArrayMethod。我们使用 Python 有限 API 所采用的槽机制。槽定义见下文。

typedef struct {
   const char *name;
   int nin, nout;
   NPY_CASTING casting;
   NPY_ARRAYMETHOD_FLAGS flags;
   PyArray_DTypeMeta **dtypes;
   PyType_Slot *slots;
} PyArrayMethod_Spec;
const char *name#

循环的名称。

int nin#

输入操作数的数量。

int nout#

输出操作数的数量。

NPY_CASTING casting#

用于指示类型转换操作的最小宽松程度。例如,如果某个类型转换操作在某些情况下可能是安全的,但在其他情况下可能不安全,则应设置 NPY_UNSAFE_CASTING。ufunc 循环不使用此字段,但仍必须设置。

NPY_ARRAYMETHOD_FLAGS flags#

为此方法设置的标志。

PyArray_DTypeMeta **dtypes#

循环的 DTypes。长度必须为 nin + nout

PyType_Slot *slots#

该方法的槽数组。槽 ID 必须是以下值之一。

PyArray_DTypeMeta 和 PyArrayDTypeMeta_Spec#

PyTypeObject PyArrayDTypeMeta_Type#

对应于 PyArray_DTypeMeta 的 Python 类型对象。

type PyArray_DTypeMeta#

一个主要不透明的结构体,表示 DType 类。每个实例定义一个 NumPy 数据类型的元类。数据类型可以是无参数的,也可以是参数化的。对于无参数类型,DType 类与从 DType 类创建的描述符实例有一一对应关系。参数化类型可以对应于许多不同的 dtype 实例,具体取决于所选参数。此类型在公共 numpy/dtype_api.h 头文件中可用。目前,在有限的 CPython API 中不支持使用此结构体,因此如果设置了 Py_LIMITED_API,此类型是 PyTypeObject 的 typedef。

typedef struct {
     PyHeapTypeObject super;
     PyArray_Descr *singleton;
     int type_num;
     PyTypeObject *scalar_type;
     npy_uint64 flags;
     void *dt_slots;
     void *reserved[3];
} PyArray_DTypeMeta
PyHeapTypeObject super#

超类,提供连接到 Python 对象 API 的钩子。设置此结构体的成员以填充实现 PyTypeObject API 的函数(例如 tp_new)。

PyArray_Descr *singleton#

一个适合用作该数据类型单例描述符的描述符实例。这对于表示简单旧式数据类型的非参数类型很有用,因为该类型的所有数据只有一个逻辑描述符实例。如果单例实例不适用,则可以为 NULL。

int type_num#

对应于旧式数据类型的类型编号。在 NumPy 之外定义的数据类型以及可能随 NumPy 提供的未来数据类型将把 type_num 设置为 -1,因此不应依赖此字段来区分数据类型。

PyTypeObject *scalar_type#

此数据类型的标量实例类型。

npy_uint64 flags#

可以设置标志来指示 NumPy 此数据类型具有可选行为。有关允许的标志值列表,请参阅标志

void *dt_slots#

指向一个私有结构体的 opaque 指针,该结构体包含 DType API 中函数的实现。这通过用于初始化 DType 的 PyArrayDTypeMeta_Spec 实例的 slots 成员填充。

type PyArrayDTypeMeta_Spec#

一个结构体,用于通过 PyArrayInitDTypeMeta_FromSpec 函数初始化新的 DType。

typedef struct {
    PyTypeObject *typeobj;
    int flags;
    PyArrayMethod_Spec **casts;
    PyType_Slot *slots;
    PyTypeObject *baseclass;
}
PyTypeObject *typeobj#

NULL 或与 DType 关联的 Python 标量类型。对数组进行标量索引会返回此类型的一个项。

int flags#

DType 类的静态标志,指示 DType 是否是参数化的、抽象的,或表示数值数据。后者是可选的,但设置它很有用,可以向后续代码指示 DType 表示的是数字(整数、浮点数或其他数值数据类型)还是其他数据(例如字符串、单位或日期)。

PyArrayMethod_Spec **casts;#

一个以 NULL 结尾的 ArrayMethod 规范数组,用于 DType 定义的类型转换。

PyType_Slot *slots;#

一个以 NULL 结尾的槽规范数组,用于 DType API 中函数的实现。槽 ID 必须是 槽 ID 和 API 函数类型定义中列举的 DType 槽 ID 之一。

暴露的 DType 类(PyArray_DTypeMeta 对象)#

为了与 promoters 一起使用,NumPy 暴露了许多遵循 PyArray_<Name>DType 模式的 Dtypes,它们与 np.dtypes 中找到的类型相对应。

此外,PyArray_PyLongDTypePyArray_PyFloatDTypePyArray_PyComplexDType 这三个 DType 对应于 Python 标量值。它们并非适用于所有场合,但允许进行常见的 dtype 操作,并且可能需要使用它们来实现类型提升。

此外,还定义了以下抽象 DType,它们涵盖了内置 NumPy 类型和 Python 类型,用户原则上可以从它们派生子类(这不继承任何 DType 特定功能):* PyArray_IntAbstractDType * PyArray_FloatAbstractDType * PyArray_ComplexAbstractDType

警告

自 NumPy 2.0 起,这些 DType 唯一有效的用途是方便地注册一个 promoter,例如匹配“任何整数”(以及子类检查)。因此,它们不暴露给 Python。

PyUFunc_Type 和 PyUFuncObject#

PyTypeObject PyUFunc_Type#

ufunc 对象是通过创建 PyUFunc_Type 来实现的。它是一个非常简单的类型,只实现了基本的 getattribute 行为、打印行为,并且具有调用行为,允许这些对象像函数一样操作。ufunc 背后的基本思想是为支持该操作的每种数据类型保存一个对快速一维(向量)循环的引用。这些一维循环都具有相同的签名,是创建新 ufunc 的关键。它们由通用循环代码在适当的时候调用,以实现 N 维函数。此外,还为浮点和复浮点数组定义了一些通用的一维循环,允许您使用单个标量函数(例如 atanh)定义 ufunc。

type PyUFuncObject#

ufunc 的核心是 PyUFuncObject,它包含调用执行实际工作的底层 C 代码循环所需的所有信息。尽管在此处对其进行描述是为了完整性,但应将其视为 NumPy 的内部构件,并通过 PyUFunc_* 函数进行操作。此结构体的大小可能会在 NumPy 版本之间发生变化。为确保兼容性

  • 永远不要声明非指针的结构实例

  • 永远不要进行指针算术

  • 切勿使用 sizeof(PyUFuncObject)

其结构如下:

typedef struct {
    PyObject_HEAD
    int nin;
    int nout;
    int nargs;
    int identity;
    PyUFuncGenericFunction *functions;
    void **data;
    int ntypes;
    int reserved1;
    const char *name;
    char *types;
    const char *doc;
    void *ptr;
    PyObject *obj;
    PyObject *userloops;
    int core_enabled;
    int core_num_dim_ix;
    int *core_num_dims;
    int *core_dim_ixs;
    int *core_offsets;
    char *core_signature;
    PyUFunc_TypeResolutionFunc *type_resolver;
    void *reserved2;
    void *reserved3;
    npy_uint32 *op_flags;
    npy_uint32 *iter_flags;
    /* new in API version 0x0000000D */
    npy_intp *core_dim_sizes;
    npy_uint32 *core_dim_flags;
    PyObject *identity_value;
    /* Further private slots (size depends on the NumPy version) */
} PyUFuncObject;
int nin#

输入参数的数量。

int nout#

输出参数的数量。

int nargs#

参数总数(nin + nout)。此值必须小于 NPY_MAXARGS

int identity#

PyUFunc_OnePyUFunc_ZeroPyUFunc_MinusOnePyUFunc_NonePyUFunc_ReorderableNonePyUFunc_IdentityValue,用于指示此操作的恒等值。它仅用于对空数组进行类似 reduce 的调用。

void functions(char **args, npy_intp *dims, npy_intp *steps, void *extradata)#

一个函数指针数组——ufunc 支持的每种数据类型一个。这是被调用来执行底层函数 dims [0] 次的向量循环。第一个参数 args 是一个指向 behaved memory 的 nargs 个指针数组。首先是输入参数数据的指针,然后是输出参数数据的指针。在序列中跳过多少字节才能到达下一个元素由 steps 数组中的相应条目指定。最后一个参数允许循环接收额外信息。这通常用于单个通用向量循环可用于多个函数的情况。在这种情况下,要调用的实际标量函数作为 extradata 传入。此函数指针数组的大小是 ntypes。

void **data#

要传递给一维向量循环的额外数据,如果不需要额外数据则为 NULL。此 C 数组必须与 functions 数组的大小相同(即 ntypes)。如果不需要 extra_data,则使用 NULL。UFuncs 的几个 C-API 调用只是利用这些额外数据来接收实际要调用的函数的指针的一维向量循环。

int ntypes#

ufunc 支持的数据类型数量。此数字指定有多少个不同的一维循环(内置数据类型)可用。

char *name#

ufunc 的字符串名称。这用于动态构建 ufunc 的 __doc__ 属性。

char *types#

一个 \(nargs \times ntypes\) 的 8 位 type_numbers 数组,其中包含每个支持(内置)数据类型的函数类型签名。对于 ntypes 个函数中的每一个,此数组中对应的类型编号集显示 args 参数应如何在一维向量循环中解释。这些类型编号不必相同,并且支持混合类型 ufuncs。

char *doc#

ufunc 的文档。不应包含函数签名,因为这在检索 __doc__ 时会动态生成。

void *ptr#

任何动态分配的内存。目前,这用于从 Python 函数创建的动态 ufunc,以存储类型、数据和名称成员的空间。

PyObject *obj#

对于从 Python 函数动态创建的 ufunc,此成员持有对底层 Python 函数的引用。

PyObject *userloops#

一个用户定义的一维向量循环(存储为 CObject 指针)的字典,用于用户定义类型。用户可以为任何用户定义类型注册循环。它通过类型编号检索。用户定义类型编号始终大于 NPY_USERDEF

int core_enabled#

标量 ufunc 为 0;广义 ufunc 为 1。

int core_num_dim_ix#

签名中不同核心维度名称的数量。

int *core_num_dims#

每个参数的核心维度数量。

int *core_dim_ixs#

扁平化形式的维度索引;参数 k 的索引存储在 core_dim_ixs[core_offsets[k] : core_offsets[k] + core_numdims[k]] 中。

int *core_offsets#

core_dim_ixs 中每个参数的第一个核心维度位置,等同于 cumsum(core_num_dims)。

char *core_signature#

核心签名字符串。

PyUFunc_TypeResolutionFunc *type_resolver#

一个函数,用于解析类型并用输入和输出的 dtype 填充数组。

type PyUFunc_TypeResolutionFunc#

type_resolver 的函数指针类型。

npy_uint32 op_flags#

覆盖每个 ufunc 操作数的默认操作数标志。

npy_uint32 iter_flags#

覆盖 ufunc 的默认 nditer 标志。

在 API 版本 0x0000000D 中添加。

npy_intp *core_dim_sizes#

对于每个不同的核心维度,如果 UFUNC_CORE_DIM_SIZE_INFERRED0,则其可能的固定大小。

npy_uint32 *core_dim_flags#

对于每个不同的核心维度,一组标志(UFUNC_CORE_DIM_CAN_IGNOREUFUNC_CORE_DIM_SIZE_INFERRED)。

PyObject *identity_value#

PyUFuncObject.identity 等于 PyUFunc_IdentityValue 时,用于归约的恒等值。

UFUNC_CORE_DIM_CAN_IGNORE#

如果维度名称以 ? 结尾。

UFUNC_CORE_DIM_SIZE_INFERRED#

如果维度大小将根据操作数而不是固定签名确定。

PyArrayIter_Type 和 PyArrayIterObject#

PyTypeObject PyArrayIter_Type#

这是一个迭代器对象,可以方便地遍历 N 维数组。它是 ndarray 的 flat 属性返回的对象。它也在整个实现内部广泛用于遍历 N 维数组。tp_as_mapping 接口已实现,因此迭代器对象可以被索引(使用一维索引),并且通过 tp_methods 表实现了一些方法。此对象实现了 next 方法,可以在 Python 中任何可以使用迭代器的地方使用。

type PyArrayIterObject#

对应于 PyArrayIter_Type 对象的 C 结构体是 PyArrayIterObjectPyArrayIterObject 用于跟踪 N 维数组中的指针。它包含用于快速遍历数组的相关信息。指针可以通过三种基本方式进行调整:1) 以 C 风格的连续方式前进到数组中的“下一个”位置,2) 前进到数组中的任意 N 维坐标,以及 3) 前进到数组中的任意一维索引。这些计算中使用了 PyArrayIterObject 结构体的成员。迭代器对象保留其自身的数组维度和步长信息。这可以根据“广播”的需要进行调整,或者仅遍历特定维度。

typedef struct {
    PyObject_HEAD
    int   nd_m1;
    npy_intp  index;
    npy_intp  size;
    npy_intp  coordinates[NPY_MAXDIMS_LEGACY_ITERS];
    npy_intp  dims_m1[NPY_MAXDIMS_LEGACY_ITERS];
    npy_intp  strides[NPY_MAXDIMS_LEGACY_ITERS];
    npy_intp  backstrides[NPY_MAXDIMS_LEGACY_ITERS];
    npy_intp  factors[NPY_MAXDIMS_LEGACY_ITERS];
    PyArrayObject *ao;
    char  *dataptr;
    npy_bool  contiguous;
} PyArrayIterObject;
int nd_m1#

\(N-1\),其中 \(N\) 是底层数组中的维度数量。

npy_intp index#

数组中当前的一维索引。

npy_intp size#

底层数组的总大小。

npy_intp *coordinates#

数组的 \(N\) 维索引。

npy_intp *dims_m1#

数组在每个维度上的大小减 1。

npy_intp *strides#

数组的步长。在每个维度中跳到下一个元素所需的字节数。

npy_intp *backstrides#

从一个维度末尾跳回其开头所需的字节数。注意 backstrides[k] == strides[k] * dims_m1[k],但此处作为优化存储。

npy_intp *factors#

此数组用于从一维索引计算 N 维索引。它包含维度所需的乘积。

PyArrayObject *ao#

指向此迭代器所代表的底层 ndarray 的指针。

char *dataptr#

此成员指向由索引指示的 ndarray 中的一个元素。

npy_bool contiguous#

如果底层数组是 NPY_ARRAY_C_CONTIGUOUS,则此标志为真。它用于在可能的情况下简化计算。

如何在一个 C 级别使用数组迭代器将在后续章节中更详细地解释。通常,您无需关心迭代器对象的内部结构,只需通过使用宏 PyArray_ITER_NEXT (it)、PyArray_ITER_GOTO (it, dest) 或 PyArray_ITER_GOTO1D (it, index) 与其交互。所有这些宏都要求参数 it 是一个 PyArrayIterObject*

PyArrayMultiIter_Type 和 PyArrayMultiIterObject#

PyTypeObject PyArrayMultiIter_Type#

此类型提供了一个封装广播概念的迭代器。它允许将 \(N\) 个数组一起广播,以便循环以 C 风格的连续方式遍历广播后的数组。对应的 C 结构体是 PyArrayMultiIterObject,其内存布局必须以传递给 PyArray_Broadcast (obj) 函数的任何对象 obj 开始。广播通过调整数组迭代器来执行,使得每个迭代器都表示广播后的形状和大小,但其步长会进行调整,以便在每次迭代中使用数组中正确的元素。

type PyArrayMultiIterObject#
typedef struct {
    PyObject_HEAD
    int numiter;
    npy_intp size;
    npy_intp index;
    int nd;
    npy_intp dimensions[NPY_MAXDIMS_LEGACY_ITERS];
    PyArrayIterObject *iters[];
} PyArrayMultiIterObject;
int numiter#

需要广播到相同形状的数组数量。

npy_intp size#

广播后的总大小。

npy_intp index#

广播结果中的当前(一维)索引。

int nd#

广播结果中的维度数量。

npy_intp *dimensions#

广播结果的形状(仅使用 nd 个槽)。

PyArrayIterObject **iters#

一个迭代器对象数组,包含要一起广播的数组的迭代器。返回时,迭代器已为广播进行调整。

PyArrayNeighborhoodIter_Type 和 PyArrayNeighborhoodIterObject#

PyTypeObject PyArrayNeighborhoodIter_Type#

这是一个迭代器对象,可以方便地遍历 N 维邻域。

type PyArrayNeighborhoodIterObject#

对应于 PyArrayNeighborhoodIter_Type 对象的 C 结构体是 PyArrayNeighborhoodIterObject

typedef struct {
    PyObject_HEAD
    int nd_m1;
    npy_intp index, size;
    npy_intp coordinates[NPY_MAXDIMS_LEGACY_ITERS]
    npy_intp dims_m1[NPY_MAXDIMS_LEGACY_ITERS];
    npy_intp strides[NPY_MAXDIMS_LEGACY_ITERS];
    npy_intp backstrides[NPY_MAXDIMS_LEGACY_ITERS];
    npy_intp factors[NPY_MAXDIMS_LEGACY_ITERS];
    PyArrayObject *ao;
    char *dataptr;
    npy_bool contiguous;
    npy_intp bounds[NPY_MAXDIMS_LEGACY_ITERS][2];
    npy_intp limits[NPY_MAXDIMS_LEGACY_ITERS][2];
    npy_intp limits_sizes[NPY_MAXDIMS_LEGACY_ITERS];
    npy_iter_get_dataptr_t translate;
    npy_intp nd;
    npy_intp dimensions[NPY_MAXDIMS_LEGACY_ITERS];
    PyArrayIterObject* _internal_iter;
    char* constant;
    int mode;
} PyArrayNeighborhoodIterObject;

标量数组类型#

数组中可以存在的每种不同的内置数据类型都有一个 Python 类型。其中大多数是 C 语言中相应数据类型的简单包装器。这些类型的 C 名称是 Py{TYPE}ArrType_Type,其中 {TYPE} 可以是

Bool, Byte, Short, Int, Long, LongLong, UByte, UShort, UInt, ULong, ULongLong, Half, Float, Double, LongDouble, CFloat, CDouble, CLongDouble, String, Unicode, Void, Datetime, Timedelta, 和 Object

这些类型名称是 C-API 的一部分,因此可以在扩展 C 代码中创建。还有 PyIntpArrType_TypePyUIntpArrType_Type,它们是平台上可以容纳指针的整数类型之一的简单替代。这些标量对象的结构不暴露给 C 代码。函数 PyArray_ScalarAsCtype (..) 可以用于从数组标量中提取 C 类型值,函数 PyArray_Scalar (…) 可以用于从 C 值构造数组标量。

其他 C 结构体#

在 NumPy 的开发中发现了一些新的 C 结构体很有用。这些 C 结构体至少在一个 C-API 调用中使用,因此在此处进行文档说明。定义这些结构体的主要原因是使其易于使用 Python ParseTuple C-API 将 Python 对象转换为有用的 C 对象。

PyArray_Dims#

type PyArray_Dims#

当需要解释形状和/或步长信息时,此结构体非常有用。结构体如下:

typedef struct {
    npy_intp *ptr;
    int len;
} PyArray_Dims;

此结构体的成员是

npy_intp *ptr#

指向整数 (npy_intp) 列表的指针,通常表示数组形状或数组步长。

int len#

整数列表的长度。假定访问 ptr [0] 到 ptr [len-1] 是安全的。

PyArray_Chunk#

type PyArray_Chunk#

这与 Python 中直到 ptr 成员的缓冲区对象结构相同。在 32 位平台上(即如果 NPY_SIZEOF_INT == NPY_SIZEOF_INTP),len 成员也与缓冲区对象的等效成员匹配。它用于表示通用的单段内存块。

typedef struct {
    PyObject_HEAD
    PyObject *base;
    void *ptr;
    npy_intp len;
    int flags;
} PyArray_Chunk;

成员包括

PyObject *base#

此内存块来源的 Python 对象。需要此信息以便正确地计算内存。

void *ptr#

指向单段内存块起点的指针。

npy_intp len#

段的长度(字节)。

int flags#

任何应用于解释内存的数据标志(例如 NPY_ARRAY_WRITEABLE)。

PyArrayInterface#

另请参阅

数组接口协议

type PyArrayInterface#

定义 PyArrayInterface 结构体是为了让 NumPy 和其他扩展模块可以使用快速数组接口协议。支持快速数组接口协议的对象的 __array_struct__ 方法应返回一个 PyCapsule,其中包含指向 PyArrayInterface 结构体的指针,以及数组的相关详细信息。创建新数组后,应 DECREF 该属性,这将释放 PyArrayInterface 结构体。请记住 INCREF 该对象(其 __array_struct__ 属性已被检索),并将新的 PyArrayObject 的 base 成员指向该同一对象。通过这种方式,数组的内存将得到正确管理。

typedef struct {
    int two;
    int nd;
    char typekind;
    int itemsize;
    int flags;
    npy_intp *shape;
    npy_intp *strides;
    void *data;
    PyObject *descr;
} PyArrayInterface;
int two#

整数 2,作为健全性检查。

int nd#

数组中的维度数量。

char typekind#

一个字符,根据类型字符串约定指示存在何种类型的数组,其中“t”-> 位域,“b”-> 布尔,“i”-> 有符号整数,“u”-> 无符号整数,“f”-> 浮点,“c”-> 复浮点,“O”-> 对象,“S”-> (字节)字符串,“U”-> Unicode,“V”-> 空。

int itemsize#

数组中每个项目所需的字节数。

int flags#

可以是位 NPY_ARRAY_C_CONTIGUOUS (1)、NPY_ARRAY_F_CONTIGUOUS (2)、NPY_ARRAY_ALIGNED (0x100)、NPY_ARRAY_NOTSWAPPED (0x200) 或 NPY_ARRAY_WRITEABLE (0x400) 中的任意一个,以指示有关数据的信息。实际中,NPY_ARRAY_ALIGNEDNPY_ARRAY_C_CONTIGUOUSNPY_ARRAY_F_CONTIGUOUS 标志可以从其他参数确定。也可以设置 NPY_ARR_HAS_DESCR (0x800) 标志,以向使用版本 3 数组接口的对象指示结构体中存在 descr 成员(使用版本 2 数组接口的对象将忽略此成员)。

npy_intp *shape#

一个数组,包含数组在每个维度上的大小。

npy_intp *strides#

一个数组,包含在每个维度上跳跃到下一个元素所需的字节数。

void *data#

指向数组第一个元素的指针。

PyObject *descr#

一个 Python 对象,更详细地描述了数据类型(与 __array_interface__ 中的 descr 键相同)。如果 typekinditemsize 提供了足够的信息,则此项可以为 NULL。此字段也仅当 flags 中设置了 NPY_ARR_HAS_DESCR 标志时才会被考虑。

内部使用的结构体#

在内部,代码使用了一些额外的 Python 对象,主要用于内存管理。这些类型不能直接从 Python 访问,也不会暴露给 C-API。此处包含它们只是为了完整性,并帮助理解代码。

type PyUFunc_Loop1d#

一个简单的 C 结构体链表,包含定义用户定义数据类型的每个已定义签名的 ufunc 的 1-d 循环所需的信息。

PyTypeObject PyArrayMapIter_Type#

此 Python 类型处理高级索引。它只是对包含高级数组索引所需变量的 C 结构体的一个松散封装。

type PyArrayMapIterObject#

PyArrayMapIter_Type 相关联的 C 结构体。如果您试图理解高级索引映射代码,此结构体将非常有用。它在 arrayobject.h 头文件中定义。此类型未暴露给 Python,并且可以用 C 结构体替换。作为 Python 类型,它利用了引用计数的内存管理。

NumPy C-API 和 C 复数#

当您使用 NumPy C-API 时,您将可以访问复数实数声明 npy_cdoublenpy_cfloat,它们是根据 complex.h 中的 C 标准类型声明的。不幸的是,complex.h 包含 #define I …` (其中实际定义取决于编译器),这意味着任何下游用户如果在其代码中执行 #include <numpy/arrayobject.h>,可能会导致 I 被定义,然后使用类似声明 double I; 的代码将导致一个晦涩难懂的编译器错误,例如:

可以通过添加以下代码来避免此错误:

#undef I

到您的代码中。

2.0 版本中更改: 在 NumPy 2 中,包含 complex.h 是新增的,因此定义不同 I 的代码在旧版本中可能不需要 #undef I。NumPy 2.0.1 曾短暂地包含 #under I