Python 类型和 C 结构体#

在 C 代码中定义了几种新类型。其中大多数都可以从 Python 访问,但由于使用有限,一些类型未公开。每个新的 Python 类型都有一个关联的 PyObject*,其内部结构包含指向“方法表”的指针,该方法表定义了新对象在 Python 中的行为。当您将 Python 对象接收到的 C 代码中时,您始终会获得指向 PyObject 结构体的指针。因为 PyObject 结构体非常通用,并且仅定义 PyObject_HEAD,所以它本身并不是很有趣。但是,不同的对象在 PyObject_HEAD 之后包含更多详细信息(但是您必须将其强制转换为正确的类型才能访问它们——或者使用访问器函数或宏)。

定义的新 Python 类型#

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

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

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

与定义 Python 类行为的特殊方法名称不同,这里有“函数表”指向实现所需结果的函数。从 Python 2.2 开始,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 数组。此类数组具有未定义的维度和步幅,无法访问。宏 PyArray_NDIMndarraytypes.h 中定义,指向此数据成员。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 对象 的许多功能,包括 tp_as_numbertp_as_sequencetp_as_mappingtp_as_buffer 接口。 富比较 也与新式属性查找一起用于成员 (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#

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

char kind#

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

char type#

一个传统字符代码,指示数据类型。

char byteorder#

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

npy_uint64 flags#

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

int type_num#

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

npy_intp elsize#

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

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

npy_intp alignment#

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

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

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#

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

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

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

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,并在指示时进行swap。arr 的值仅用于灵活的(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用于检索灵活数组的itemsize和字段信息。

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

指向一个函数的指针,该函数用于在以data指向的元素开始的arr数组中,查找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)#

指向一个函数的指针,如果arr数组中由data指向的元素不为零,则返回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中(必须使用索引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)#

指向一个函数的指针,该函数用于在以data指向的元素开始的arr数组中,查找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#

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

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#

循环的 DType。长度必须为 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 此数据类型具有可选行为。有关允许的标志值的列表,请参阅 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#

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

int flags#

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

PyArrayMethod_Spec **casts;#

DType 定义的强制转换的 ArrayMethod 规范的以 NULL 结尾的数组。

PyType_Slot *slots;#

DType API 中函数实现的槽规范的以 NULL 结尾的数组。槽 ID 必须是 Slot IDs and API Function Typedefs 中枚举的 DType 槽 ID 之一。

公开的 DTypes 类(PyArray_DTypeMeta 对象)#

为了与提升器一起使用,NumPy 公开了许多 DTypes,遵循 PyArray_<Name>DType 模式,对应于在 np.dtypes 中找到的那些。

此外,三个 DTypes,PyArray_PyLongDTypePyArray_PyFloatDTypePyArray_PyComplexDType 对应于 Python 标量值。这些不能在所有地方使用,但确实允许例如常见的 dtype 操作,并且可能需要使用它们来实现提升。

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

警告

从 NumPy 2.0 开始,这些 DTypes 的*唯一*有效用途是方便地注册提升器,例如匹配“任何整数”(以及子类检查)。因此,它们不会公开给 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 来指示此操作的单位元。它仅用于对空数组进行类似归约的调用。

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 调用只是一维向量循环,它们利用此额外数据接收指向要调用的实际函数的指针。

int ntypes#

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

char *name#

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

char *types#

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

char *doc#

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

void *ptr#

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

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,则可能存在的 frozen 大小

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-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) 与其交互。所有这些宏都要求参数 itPyArrayIterObject*

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_Type 和一个 PyUIntpArrType_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’ -> 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 键相同)。如果 typekinditemsize 提供了足够的信息,则它可以是 NULL。除非 flags 中的 NPY_ARR_HAS_DESCR 标记已打开,否则此字段也会被忽略。

内部使用的结构#

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

type PyUFunc_Loop1d#

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

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 中的更改: complex.h 的包含是 NumPy 2 中的新内容,因此定义了不同 I 的代码可能不需要在旧版本上使用 #undef I。NumPy 2.0.1 简要包含了 #under I