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 类行为的特殊方法名称,这里是"函数表",指向实现所需结果的函数.PyTypeObject 本身是动态的,允许 C 类型在 C 中从其他 C 类型"子类型化",并在 Python 中进行子类化.子类型继承来自其父类的属性和方法.

有两个主要的新类型:ndarray ( PyArray_Type ) 和 ufunc ( PyUFunc_Type ).其他类型起辅助作用: PyArrayIter_Type , PyArrayMultiIter_TypePyArrayDescr_Type . PyArrayIter_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_CONTIGUOUS , NPY_ARRAY_F_CONTIGUOUS , NPY_ARRAY_OWNDATA , NPY_ARRAY_ALIGNED , NPY_ARRAY_WRITEABLE , NPY_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_number , tp_as_sequence , tp_as_mappingtp_as_buffer 接口. rich comparison ) 也与成员 ( tp_members ) 和属性 ( tp_getset ) 的新式属性查找一起使用. PyArray_Type 也可以被子类型化.

小技巧

The tp_as_number methods use a generic approach to call whatever function has been registered for handling the operation. When the _multiarray_umath module is imported, it sets the numeric operations for all arrays to the corresponding ufuncs. This choice can be changed with PyUFunc_ReplaceLoopBySignature.

PyGenericArrType_Type#

PyTypeObject PyGenericArrType_Type#

`` PyGenericArrType_Type `` 是 PyTypeObject 的定义,用于创建 `` numpy.generic` python 类型.

PyArrayDescr_Type 和 PyArray_Descr#

PyTypeObject PyArrayDescr_Type#

The PyArrayDescr_Type is the built-in type of the data-type-descriptor objects used to describe how the bytes comprising the array are to be interpreted. There are 21 statically-defined PyArray_Descr objects for the built-in data-types. While these participate in reference counting, their reference count should never reach zero. There is also a dynamic table of user-defined PyArray_Descr objects that is also maintained. Once a data-type-descriptor object is “registered” it should never be deallocated either. The function PyArray_DescrFromType (…) can be used to retrieve a PyArray_Descr object from an enumerated type-number (either built-in or user- defined).

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_NAMES , PyDataType_FIELDS , PyDataType_SUBARRAY ` 访问的其他成员,在某些情况下(时间)还可以通过 ` PyDataType_C_METADATA` 访问.

PyTypeObject *typeobj#

Pointer to a typeobject that is the corresponding Python type for the elements of this array. For the builtin types, this points to the corresponding array scalar. For user-defined types, this should point to a user-defined typeobject. This typeobject can either inherit from array scalars or not. If it does not inherit from array scalars, then the NPY_USE_GETITEM and NPY_USE_SETITEM flags should be set in the flags member.

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_ELSIZE ` 和 ` PyDataType_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#

表明此数据类型的数组在 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-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#

实现内部功能的函数.并非所有这些函数指针都必须为给定的类型定义.所需的成员是 nonzero , copyswap , copyswapn , setitem , getitemcast .这些被假定为非 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;

在函数指针的描述中使用了一个表现良好的段的概念. 表现良好的段是对于数据类型对齐且以本机机器字节顺序排列的段. nonzero , copyswap , copyswapn , getitemsetitem 函数可以(并且必须)处理行为不端的数组.其他函数需要表现良好的内存段.

备注

这些函数在很大程度上是遗留 API,但是,某些函数仍然在使用. 从NumPy 2.x开始,它们只能通过 PyDataType_GetArrFuncs 获得(有关更多详细信息,请参见该函数). 在使用struct中定义的任何函数之前,应检查它是否为 NULL . 一般来说,可以预期定义了函数 getitem , setitem , copyswapcopyswapn ,但是期望所有函数都替换为较新的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_STRING , NPY_UNICODENPY_VOID )数组(并从 arr->descr->elsize 获得).第二个函数复制单个值,而第一个函数使用提供的步幅循环 n 个值.这些函数可以处理行为不端的 src 数据.如果 src 为 NULL,则不执行复制.如果 swap 为 0,则不进行字节交换.假设 dest 和 src 不重叠.如果它们重叠,则首先使用 memmove (…) ,然后使用带有 NULL 值的 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 的序列相乘,将它们相加,并将结果放入 arrop 指向的元素中.两个序列的起始位置分别由 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)#

一个指向函数的指针,该函数用给定的 length 的连续 buffer 填充一个地址已知的标量 value .最后一个参数是数组,需要该数组来获取可变长度数组的 itemsize.

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

指向特定排序算法的函数指针数组.可以使用一个键来获取特定的排序算法(目前定义了 NPY_QUICKSORT , NPY_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#

NULLNPY_NSCALARKINDS 指针数组.这些指针都应该是 NULL 或指向整数数组的指针(以 NPY_NOTYPE 结尾),指示指定类型的标量可以安全地转换为哪些数据类型(通常意味着不会丢失精度).

int *cancastto#

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

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

指向一个函数的指针,该函数检索 arrn 个元素的最小值索引,从 data 指向的元素开始.此函数要求内存段是连续且表现良好的.返回值始终为 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 指示此数据类型具有可选行为.有关允许的标志值列表,请参见 标志 .

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

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

PyType_Slot *slots;#

DType API 中函数实现的槽规范的以 NULL 结尾的数组.槽 ID 必须是 槽位 ID 和 API 函数类型定义 中枚举的 DType 槽 ID 之一.

公开的 DTypes 类 ( PyArray_DTypeMeta 对象)#

为了与 promoters 一起使用,NumPy 公开了一些遵循 PyArray_<Name>DType 模式的 Dtypes,这些模式与 np.dtypes 中的 Dtypes 相对应.

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

此外,定义了以下抽象 DTypes,它们涵盖了内置 NumPy 和 python DTypes,原则上用户可以从中继承(这不会继承任何 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 维函数.还为浮点和复数浮点数组定义了一些通用的一维循环,允许您使用单个标量函数(例如 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_One , PyUFunc_Zero , PyUFunc_MinusOne , PyUFunc_None , PyUFunc_ReorderableNonePyUFunc_IdentityValue ,以指示此操作的标识.它仅用于对空数组的类似 reduce 的调用.

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 支持的数据类型数量. 此数字指定有多少个不同的 1-d 循环(内置数据类型)可用.

char *name#

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

char *types#

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

char *doc#

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

void *ptr#

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

PyObject *obj#

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

PyObject *userloops#

用户定义的 1-d 向量循环(存储为 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#

对于每个不同的核心维度,如果 frozen0 ,则为可能的 UFUNC_CORE_DIM_SIZE_INFERRED 大小

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#

如果 dim 名称以 ? 结尾

UFUNC_CORE_DIM_SIZE_INFERRED#

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

PyArrayIter_Type 和 PyArrayIterObject#

PyTypeObject PyArrayIter_Type#

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

type PyArrayIterObject#

PyArrayIter_Type 对象的 C 结构对应于 PyArrayIterObject . PyArrayIterObject 用于跟踪指向 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#

数组中当前的 1-d 索引.

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#

此数组用于从 1-d 索引计算 N-d 索引.它包含所需维度的乘积.

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#

当前 (1-d) 索引到广播结果中.

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

在 ptr 成员之前,这等效于 Python 中的 buffer 对象结构.在 32 位平台上(即,如果 NPY_SIZEOF_INT == NPY_SIZEOF_INTP ),len 成员也与 buffer 对象的等效成员匹配.它可用于表示通用的单段内存块.

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_ALIGNED , NPY_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 . 除非标志 NPY_ARR_HAS_DESCR 在 flags 中,否则此字段也会被忽略.

内部使用的结构体#

在内部,该代码主要使用一些其他 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