Python 类型和 C 结构#

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

定义的新 Python 类型#

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

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

  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 指向,此数据成员表示解释 data 指向的内存的方式的标志。可能的标志是 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 模块时,它会将所有数组的数值运算设置为相应的 ufuncs。此选择可以通过 PyUFunc_ReplaceLoopBySignature 更改。

PyGenericArrType_Type#

PyTypeObject PyGenericArrType_Type#

PyGenericArrType_Type 是 PyTypeObject 定义,它创建 numpy.generic Python 类型。

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 访问,在某些情况下(times)还可以通过 PyDataType_C_METADATA 访问。

PyTypeObject *typeobj#

指向类型对象的指针,该对象是此数组元素的相应 Python 类型。对于内置类型,它指向相应的数组标量。对于用户定义的类型,它应指向用户定义的类型对象。此类型对象可以继承自数组标量,也可以不继承。如果它不继承自数组标量,则应在 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#

一个数字,提供此数据类型的对齐信息。具体来说,它显示了在具有第一个元素为 char 的 2 元素结构开始处的偏移量,编译器在该位置放置了此类型的一个项: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#

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

NPY_ITEM_IS_POINTER#

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

NPY_NEEDS_INIT#

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

NPY_NEEDS_PYAPI#

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

NPY_USE_GETITEM#

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

NPY_USE_SETITEM#

当从数组标量创建 0-d 数组时,使用 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。这些假定为非 NULL,而 NULL 条目将导致程序崩溃。其他函数可能为 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_Packsetitem 的功能更强大的版本,例如它能正确处理转换。

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

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

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

指向一个函数的指针,该函数从数组对象 arr 的单个元素(由 data 指向)返回一个标准的 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,如果指示,则进行交换。 arr 的值仅用于灵活数组( NPY_STRINGNPY_UNICODENPY_VOID)(并从 arr->descr->elsize 获取)。第二个函数复制单个值,第一个函数循环 n 个值,并使用提供的步幅。这些函数可以处理不正确的 src 数据。如果 src 为 NULL,则不执行复制。如果 swap 为 0,则不发生字节交换。假定 destsrc 不重叠。如果重叠,请先使用 memmove (…) 然后使用 NULL 值的 src 调用 copyswap(n)

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

指向一个函数的指针,该函数比较数组 arr 中由 d1d2 指向的两个元素。此函数需要正确的(已对齐且未交换)数组。返回值是 1 如果 * d1 > * d2,0 如果 * d1 == * d2,-1 如果 * d1 < * d2。数组对象 arr 用于检索灵活数组的 itemsize 和字段信息。

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)#

指向一个函数的指针,该函数返回 TRUE,如果 arr 中由 data 指向的项不为零。此函数可以处理不正确的数组。

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

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

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

指向一个函数的指针,该函数用给定的单个标量 value 填充给定 length 的连续 buffer,该标量 value 的地址已给出。最后一个参数是数组,它对于可变长度数组获取 itemsize 是必需的。

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 中(必须用从 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;
    void *parameters;
} PyArrayMethod_Context
PyObject *caller#

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

struct PyArrayMethodObject_tag *method#

方法“self”。目前此对象是指针类型。

PyArray_Descr **descriptors#

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

void *parameters#

指向一个结构体的指针,该结构体包含循环运行时所需的任何参数。如果没有参数需要,则为 NULL。结构体的类型特定于注册的函数。

版本 2.4 中已更改: 2.4 版本在 NumPy 中添加了 parameters 成员。

type PyArrayMethod_Spec#

用于向 NumPy 注册 ArrayMethod 的结构体。我们使用 Python 受限 API 使用的 slots 机制。请参阅下面的 slot 定义。

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#

方法的 slot 数组。Slot ID 必须是以下值之一。

PyArray_DTypeMeta 和 PyArrayDTypeMeta_Spec#

PyTypeObject PyArrayDTypeMeta_Type#

PyArray_DTypeMeta 对应的 Python 类型对象。

type PyArray_DTypeMeta#

一个基本不透明的结构,用于表示 DType 类。每个实例定义了一个 NumPy 数据类型的元类。数据类型可以是无参的或有参的。对于无参类型,DType 类与从 DType 类创建的描述符实例有一一对应关系。有参类型可以对应于许多不同的 dtype 实例,具体取决于选择的参数。此类型在公共 numpy/dtype_api.h 头文件中可用。目前在 Python 受限 C 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 此数据类型具有可选行为。有关允许的标志值,请参阅 Flags

void *dt_slots#

指向私有结构体的不透明指针,该结构体包含 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#

与 DType 关联的 Python 标量类型,可以是 NULL。对数组进行标量索引将返回具有此类型的项。

int flags#

DType 类的静态标志,指示 DType 是否为参数化、抽象或表示数值数据。后者是可选的,但有助于指示下游代码 DType 是否表示数字(整数、浮点数或其他数值数据类型)或其他内容(例如,字符串、单位或日期)。

PyArrayMethod_Spec **casts;#

由 DType 定义的转换的 ArrayMethod 规范的 NULL 终止数组。

PyType_Slot *slots;#

DType API 中函数实现的 slot 规范的 NULL 终止数组。Slot ID 必须是 Slot IDs and API Function Typedefs 中枚举的 DType slot ID 之一。

公开的 DType 类(PyArray_DTypeMeta 对象)#

用于 promoters,NumPy 公开了一些 DTypes,遵循 PyArray_<Name>DType 的模式,对应于 np.dtypes 中的 DTypes。

此外,还有三个 DTypes:PyArray_PyLongDTypePyArray_PyFloatDTypePyArray_PyComplexDType,分别对应 Python 标量值。这些不能在所有地方使用,但例如允许常见的 dtype 操作,并且使用它们来实现 promotion 可能很有必要。

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

警告

截至 NumPy 2.0,这些 DTypes 的*唯一*有效用法是方便地注册一个 promoter 以匹配“任何整数”(以及子类检查)。因此,它们不向 Python 公开。

PyUFunc_Type 和 PyUFuncObject#

PyTypeObject PyUFunc_Type#

ufunc 对象通过创建 PyUFunc_Type 来实现。它是一个非常简单的类型,仅实现基本的 getattribute 行为、打印行为,并具有允许这些对象像函数一样工作的调用行为。ufunc 的基本思想是为支持该操作的每种数据类型维护对快速一维(向量)循环的引用。这些一维循环都具有相同的签名,是创建新 ufunc 的关键。它们由通用循环代码适当地调用,以实现 N 维函数。此外,还为浮点数和复浮点数数组定义了一些通用的 1-d 循环,允许您使用单个标量函数(*例如*,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#

对于空的数组执行类似 reduce 的调用时,表示操作的身份元素,可以是 PyUFunc_OnePyUFunc_ZeroPyUFunc_MinusOnePyUFunc_NonePyUFunc_ReorderableNonePyUFunc_IdentityValue

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

函数指针数组,每个指针对应 ufunc 支持的一种数据类型。这是调用以实现底层函数 `dims`[0] 次的一维向量循环。第一个参数 `args` 是 `nargs` 指针数组,指向行为良好的内存。输入参数的数据指针在前,输出参数的数据指针在后。要跳到序列中的下一个元素必须跳过的字节数由 `steps` 数组中相应的条目指定。最后一个参数允许循环接收额外信息。这通常用于使单个通用向量循环能够用于多个函数。在这种情况下,要调用的实际标量函数作为 `extradata` 传递。此函数指针数组的大小为 ntypes。

void **data#

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

int ntypes#

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

char *name#

ufunc 的字符串名称。此名称在运行时用于动态构建 ufuncs 的 __doc__ 属性。

char *types#

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

char *doc#

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

void *ptr#

任何动态分配的内存。目前,这用于从 Python 函数动态创建的 ufuncs,以存储 types、data 和 name 成员的空间。

PyObject *obj#

对于从 Python 函数动态创建的 ufuncs,此成员保存对底层 Python 函数的引用。

PyObject *userloops#

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

int core_enabled#

标量 ufuncs 为 0;广义 ufuncs 为 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#

如果维度大小将从操作数而不是从frozen签名确定

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\) 是底层数组的维度数。

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,则此标志为 true。它用于在可能的情况下简化计算。

在 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;

ScalarArrayTypes#

对于数组中存在的每种不同的内置数据类型,都有一个 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#

在 ptr 成员之前,这等同于 Python 中的缓冲区对象结构。在 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' -> void。

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* 键相同)。如果 *typekind* 和 *itemsize* 提供了足够的信息,则此项可以为 NULL。除非 *flags* 中 NPY_ARR_HAS_DESCR 标志为 true,否则也将忽略此字段。

内部使用的结构#

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

type PyUFunc_Loop1d#

一个简单的 C 结构链表,包含为用户定义的数据类型定义 ufunc 的一维循环所需的信息。

PyTypeObject PyArrayMapIter_Type#

高级索引使用此 Python 类型处理。它只是 C 结构的一个松散包装,其中包含高级数组索引所需的变量。

type PyArrayMapIterObject#

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

NumPy C-API 和 C complex#

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

此错误可以通过添加

#undef I

到您的代码中来避免。

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