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 typeobject 的指针。

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)和一个指向 typeobject 的指针(ob_type)。(如果 Python 使用特殊选项编译,则还可能存在其他元素,有关更多信息,请参阅 Python 源代码树中的 Include/object.h)。ob_type 成员指向 Python 类型对象。

char *data#

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

int nd#

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

npy_intp *dimensions#

一个整数数组,只要 nd ≥ 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 typeobject 实现了 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。

有关以 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,这仅意味着该数据类型的功能减少。(此外,如果注册用户定义的数据类型时为 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* 给出,参数 *fromarr* 和 *toarr* 被解释为 PyArrayObjects(用于灵活的数组),以获取 itemsize 信息。

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,并在指示的情况下进行交换。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指向的两个元素。此函数需要行为良好的(已对齐且未交换)数组。如果*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指向的元素开始的arrn个元素中最大的元素的索引。此函数要求内存段是连续的且行为良好的。返回值始终为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的情况下调用,并且必须获取它才能进行错误报告。

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

一个指向函数的指针,该函数将由str指向的字符串转换为对应类型的单个元素,并将其放置在由ip指向的内存位置。转换完成后,*endptr指向字符串的其余部分。最后一个参数arr是ip指向的数组(对于可变大小的数据类型需要)。成功返回0,失败返回-1。需要行为良好的数组。此函数应在不持有Python 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,该标量的地址已给出。最后一个参数是数组,它需要获取变长数组的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或包含数据的零维数组(如果需要确定标量的类型)。返回值必须是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#

循环的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定义的转换的数组方法规范的以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。

此外,三个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]次。第一个参数argsnargs个指向良好内存的指针数组。指向输入参数数据的指针位于前面,后面跟着指向输出参数数据的指针。要获取序列中的下一个元素必须跳过多少字节由steps数组中的相应条目指定。最后一个参数允许循环接收额外信息。这通常用于单个通用向量循环可用于多个函数的情况。在这种情况下,要调用的实际标量函数作为extradata传入。此函数指针数组的大小为ntypes。

void **data#

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

int ntypes#

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

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 ptrs)的字典。用户可以为任何用户定义的类型注册循环。它通过类型号检索。用户定义的类型号总是大于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#

解析类型并使用dtypes填充输入和输出数组的函数

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#

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

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,则此标志为 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;

标量数组类型#

对于数组中可能存在的每种不同的内置数据类型,都有一个 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__属性)并将新PyArrayObjectbase成员指向同一个对象。这样,数组的内存将得到正确的管理。

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