Python 类型和 C 结构#
在C代码中定义了几种新的类型.大多数这些类型可以从Python访问,但有一些由于其有限的使用而未被暴露.每个新的Python类型都有一个关联的 PyObject* ,其内部结构包括一个指向”方法表”的指针,该方法表定义了新对象在Python中的行为方式.当你将一个Python对象接收到C代码中时,你总是得到一个指向 PyObject
结构的指针.因为 PyObject
结构非常通用,并且只定义了 PyObject_HEAD
,所以它本身并不是很有趣.然而,不同的对象在 PyObject_HEAD
之后包含更多细节(但你必须转换为正确的类型才能访问它们——或者使用访问函数或宏).
新定义的 Python 类型#
Python 类型在 C 语言中相当于 Python 中的类.通过构造一个新的 Python 类型,你为 Python 提供了一个新的对象.ndarray 对象是 C 语言中定义的新类型的一个例子.新类型在 C 语言中通过两个基本步骤定义:
创建一个C结构(通常命名为
Py{Name}Object
),该结构与PyObject
结构本身二进制兼容,但包含该特定对象所需的额外信息;填充
PyTypeObject
表(由PyObject
结构的 ob_type 成员指向)与实现类型所需行为的函数指针.
与定义Python类行为的特殊方法名称不同,存在指向实现所需结果的函数的”函数表”.自Python 2.2以来,PyTypeObject本身变得动态,这允许可以在C中从其他C类型”子类型化”的C类型,并在Python中子类化.子类型继承其父类型的属性和方法.
有两种主要的新类型:ndarray( PyArray_Type
)和 ufunc( PyUFunc_Type
).其他类型起支持作用:PyArrayIter_Type
、PyArrayMultiIter_Type
和 PyArrayDescr_Type
.:c:data:PyArrayIter_Type 是 ndarray 的平面迭代器类型(获取 flat 属性时返回的对象).:c:data:PyArrayMultiIter_Type 是调用 broadcast
时返回的对象类型,它处理迭代和广播一组嵌套序列.此外,:c:data: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#
The
PyArrayObject
C-structure contains all of the required information for an array. All instances of an ndarray (and its subclasses) will have this structure. For future compatibility, these structure members should normally be accessed using the provided macros. If you need a shorter name, then you can make use ofNPY_AO
(deprecated) which is defined to be equivalent toPyArrayObject
. Direct access to the struct fields are deprecated. Use thePyArray_*(arr)
form instead. As of NumPy 1.20, the size of this struct is not considered part of the NumPy ABI (see note at the end of the member list).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_NDIM
在ndarraytypes.h
中定义,指向此数据成员.NPY_MAXDIMS
被定义为一个编译时常量,限制维数的数量.这个数字在 NumPy 2 及以后版本中是 64,在此之前是 32.然而,我们可能希望在未来移除这个限制,因此最好显式检查依赖于这种上限的代码的维数.
-
npy_intp *dimensions#
一个整数数组,提供每个维度的形状,只要 nd \(\geq\) 1.整数总是足够大以在平台上保存指针,因此维度大小仅受内存限制.:c:data:PyArray_DIMS 是与此数据成员相关的宏.
-
npy_intp *strides#
一个整数数组,为每个维度提供必须跳过的字节数,以获取该维度中的下一个元素.与宏
PyArray_STRIDES
相关联.
-
PyObject *base#
由
PyArray_BASE
指向,这个成员用于保存指向与此数组相关的另一个Python对象的指针.有两种使用情况:如果这个数组不拥有自己的内存,那么 base 指向拥有它的 Python 对象(可能是另一个数组对象)
如果这个数组设置了
NPY_ARRAY_WRITEBACKIFCOPY
标志,那么这个数组是”行为不端”数组的工作副本.
当调用
PyArray_ResolveWritebackIfCopy
时,由 base 指向的数组将被更新为此数组的内容.
-
PyArray_Descr *descr#
指向一个数据类型描述符对象的指针(见下文).数据类型描述符对象是一个新内置类型的实例,允许对内存进行通用描述.每个支持的数据类型都有一个描述符结构.该描述符结构包含有关类型的有用信息,以及指向实现特定功能的函数指针表的指针.顾名思义,它与宏
PyArray_DESCR
相关联.
-
int flags#
由宏
PyArray_FLAGS
指向,这个数据成员表示标志,指示如何解释由数据指向的内存.可能的标志是NPY_ARRAY_C_CONTIGUOUS
、NPY_ARRAY_F_CONTIGUOUS
、NPY_ARRAY_OWNDATA
、NPY_ARRAY_ALIGNED
、NPY_ARRAY_WRITEABLE
、NPY_ARRAY_WRITEBACKIFCOPY
.
备注
进一步的成员被认为是私有的且依赖于版本.如果结构的大小对您的代码很重要,必须特别小心.当这一点相关时的一个可能用例是在 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 版本兼容的解决方案需要使用运行时计算偏移量和分配大小.
The PyArray_Type
typeobject implements many of the features of
Python objects
including the tp_as_number
, tp_as_sequence
, tp_as_mapping
, and tp_as_buffer
interfaces. The rich comparison
) is also used along with new-style attribute lookup for
member (tp_members
) and properties
(tp_getset
).
The PyArray_Type
can also be sub-typed.
小技巧
tp_as_number
方法使用一种通用的方法来调用为处理操作而注册的任何函数.当 _multiarray_umath
模块被导入时,它将所有数组的数值操作设置为相应的 ufuncs.这个选择可以通过 PyUFunc_ReplaceLoopBySignature
进行更改.
PyGenericArrType_Type#
-
PyTypeObject PyGenericArrType_Type#
PyGenericArrType_Type
是创建numpy.generic
python 类型的 PyTypeObject 定义.
PyArrayDescr_Type 和 PyArray_Descr#
-
PyTypeObject PyArrayDescr_Type#
The
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;
一些数据类型有额外的成员,这些成员可以通过 PyDataType_NAMES、PyDataType_FIELDS、PyDataType_SUBARRAY 访问,在某些情况下(时间)还可以通过 PyDataType_C_METADATA 访问.
-
PyTypeObject *typeobj#
指向一个类型对象的指针,该类型对象是此数组元素对应的 Python 类型.对于内置类型,这指向相应的数组标量.对于用户定义的类型,这应该指向一个用户定义的类型对象.这个类型对象可以继承自数组标量,也可以不继承.如果不继承自数组标量,则应在
flags
成员中设置NPY_USE_GETITEM
和NPY_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_ELSIZE 和 PyDataType_SET_ELSIZE 以一种与 NumPy 1.x 兼容的方式访问此字段.
-
npy_intp alignment#
一个提供此数据类型对齐信息的数字.具体来说,它显示了从2元素结构(其第一个元素是
char
)的起始位置开始,编译器将此类型项放置的距离:offsetof(struct {char c; type v;}, v)
请参阅 PyDataType_ALIGNMENT 以一种与 NumPy 1.x 兼容的方式访问此字段.
-
NpyAuxData *c_metadata#
特定于特定 dtype 的 C 实现的元数据.为 NumPy 1.7.0 添加.
-
type npy_hash_t#
-
npy_hash_t *hash#
用于缓存哈希值.
-
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#
实现内部功能的函数.并非所有这些函数指针都必须为给定类型定义.必需的成员是
nonzero
,copyswap
,copyswapn
,setitem
,getitem
, 和cast
.这些假定为非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
、getitem
和setitem
函数可以(并且必须)处理行为不当的数组.其他函数需要行为良好的内存段.备注
这些函数主要是遗留API,然而,一些仍然在使用.在NumPy 2.x中,它们只能通过 PyDataType_GetArrFuncs 获取(详见该函数).在使用结构体中定义的任何函数之前,你应该检查它是否为
NULL
.一般来说,函数getitem
、setitem
、copyswap
和copyswapn
可以被预期定义,但所有函数都预期被新的API替换.例如,``PyArray_Pack`` 是setitem
的一个更强大的版本,例如正确处理类型转换.-
void cast(void *from, void *to, npy_intp n, void *fromarr, void *toarr)#
一个函数指针数组,用于从当前类型转换到所有其他内置类型.每个函数将由 from 指向的连续、对齐且未交换的缓冲区转换为由 to 指向的连续、对齐且未交换的缓冲区.要转换的项数由 n 给出,参数 fromarr 和 toarr 被解释为 PyArrayObjects,用于灵活数组以获取项大小信息.
-
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_STRING
,NPY_UNICODE
,和NPY_VOID
)数组(并且从arr->descr->elsize
获取).第二个函数复制单个值,而第一个函数则使用提供的步长循环遍历 n 个值.这些函数可以处理行为不当的 src 数据.如果 src 为 NULL,则不执行复制.如果 swap 为 0,则不进行字节交换.假设 dest 和 src 不重叠.如果它们重叠,则先使用memmove
(…) ,然后使用值为 NULL 的src
进行copyswap(n)
.
-
int compare(const void *d1, const void *d2, void *arr)#
一个指向比较数组
arr
中由d1
和d2
指向的两个元素的函数的指针.这个函数要求行为良好的(对齐且未交换的)数组.如果 *d1
> *d2
,返回值为1;如果 *d1
== *d2
,返回值为0;如果 *d1
< *d2
,返回值为-1.数组对象arr
用于检索灵活数组的项大小和字段信息.
-
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
指向的元素.两个序列的起始位置分别由ip1
和ip2
指向.要到达每个序列的下一个元素,分别需要跳转is1
和is2
字节.该函数需要行为良好(尽管不一定连续)的内存.
-
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,并且必须在错误报告时获取它.
-
void fill(void *data, npy_intp length, void *arr)#
一个指向函数的指针,该函数用数据填充给定长度的连续数组.数组的前两个元素必须已经填充.从这两个值中,将计算出一个增量,并且从第3项到末尾的值将通过重复添加这个计算出的增量来计算.数据缓冲区必须表现良好.
-
void fillwithscalar(void *buffer, npy_intp length, void *value, void *arr)#
一个指向函数的指针,该函数用给定的
length
填充连续的buffer
,其中包含一个标量value
,其地址已给出.最后一个参数是需要获取变长数组项大小的数组.
-
int sort(void *start, npy_intp length, void *arr)#
一个指向特定排序算法函数指针的数组.使用一个键(目前定义了
NPY_QUICKSORT
、NPY_HEAPSORT
和NPY_MERGESORT
)可以获得特定的排序算法.这些排序是在原地进行的,假设数据是连续且对齐的.
-
int argsort(void *start, npy_intp *result, npy_intp length, void *arr)#
一个指向该数据类型排序算法的函数指针数组.与排序相同的排序算法是可用的.排序产生的索引在
result
中返回(必须用从 0 到length-1
的索引初始化).
-
NPY_SCALARKIND scalarkind(PyArrayObject *arr)#
一个用于确定此类型的标量应如何解释的函数.参数是
NULL
或包含数据的0维数组(如果需要这些数据来确定标量的种类).返回值必须是NPY_SCALARKIND
类型.
-
int **cancastscalarkindto#
要么是
NULL
,要么是一个NPY_NSCALARKINDS
指针数组.这些指针中的每一个都应该是NULL
或指向一个整数数组的指针(以NPY_NOTYPE
结束),表示该数据类型的标量可以安全转换到的数据类型(这通常意味着不会丢失精度).
-
int *cancastto#
可以是
NULL
或一个整数数组(以NPY_NOTYPE
结束),指示此数据类型可以安全转换到的数据类型(这通常意味着不会丢失精度).
-
void cast(void *from, void *to, npy_intp n, void *fromarr, void *toarr)#
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
-
struct PyArrayMethodObject_tag *method#
方法 “self”.目前这个对象是一个不透明的指针.
-
PyArray_Descr **descriptors#
由
resolve_descriptors
填充的 ufunc 循环的描述符数组.数组的长度为nin
+nout
.
-
struct PyArrayMethodObject_tag *method#
-
type PyArrayMethod_Spec#
用于向NumPy注册ArrayMethod的结构.我们使用Python有限API中使用的插槽机制.插槽定义见下文.
typedef struct { const char *name; int nin, nout; NPY_CASTING casting; NPY_ARRAYMETHOD_FLAGS flags; PyArray_DTypeMeta **dtypes; PyType_Slot *slots; } PyArrayMethod_Spec;
-
const char *name#
循环的名称.
-
int nin#
输入操作数的数量
-
int nout#
输出操作数的数量.
-
NPY_CASTING casting#
用于指示一个类型转换操作的最小许可程度.例如,如果一个类型转换操作在某些情况下可能是安全的,但在其他情况下是不安全的,那么应设置
NPY_UNSAFE_CASTING
.不用于ufunc循环,但仍必须设置.
-
NPY_ARRAYMETHOD_FLAGS flags#
为该方法设置的标志.
-
PyArray_DTypeMeta **dtypes#
循环的DTypes.长度必须是
nin
+nout
.
-
PyType_Slot *slots#
方法的插槽数组.插槽ID必须是以下值之一.
-
const char *name#
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#
一个指向私有结构的不透明指针,该结构包含DType API中函数的实现.这是从用于初始化DType的``PyArrayDTypeMeta_Spec``实例的``slots``成员填充的.
-
PyHeapTypeObject super#
-
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;#
一个以
NULL
结尾的槽规范数组,用于 DType API 中函数的实现.槽 ID 必须是 槽ID和API函数类型定义 中枚举的 DType 槽 ID 之一.
-
PyTypeObject *typeobj#
暴露的 DTypes 类(PyArray_DTypeMeta
对象)#
对于与促进剂一起使用,NumPy 暴露了一些遵循 PyArray_<Name>DType
模式的 Dtype,对应于 np.dtypes 中找到的那些.
此外,三种DTypes,``PyArray_PyLongDType``、PyArray_PyFloatDType
、PyArray_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
来实现.它是一个非常简单的类型,只实现了基本的获取属性行为、打印行为,并且具有调用行为,这使得这些对象可以像函数一样工作.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_ReorderableNone
,或PyUFunc_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 数组的大小必须与函数数组相同(即 i.e. ntypes).如果不需要 extra_data,则使用NULL
.UFuncs 的几个 C-API 调用只是一维向量循环,利用这些额外数据接收指向实际调用函数的指针.
-
int ntypes#
ufunc 支持的数据类型数量.这个数量指定了有多少不同的 1-d 循环(针对内置数据类型)是可用的.
-
char *name#
ufunc 的字符串名称.这用于动态构建 ufuncs 的 __doc__ 属性.
-
char *types#
一个 \(nargs imes ntypes\) 8-位 type_numbers 数组,包含每个支持(内置)数据类型的函数类型签名.对于每个 ntypes 函数,此数组中相应的类型号集显示了在 1-维向量循环中应如何解释 args 参数.这些类型号不必相同,并且支持混合类型 ufuncs.
-
char *doc#
ufunc 的文档.不应包含函数签名,因为这是在检索 __doc__ 时动态生成的.
-
void *ptr#
任何动态分配的内存.目前,这是用于从python函数创建的动态ufuncs,以存储类型的空间、数据和名称成员.
-
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#
一个解析类型并为输入和输出用 dtypes 填充数组的函数
-
type PyUFunc_TypeResolutionFunc#
用于
type_resolver
的函数指针类型
-
type PyUFunc_TypeResolutionFunc#
-
npy_uint32 op_flags#
覆盖每个ufunc操作数的默认操作数标志.
-
npy_uint32 iter_flags#
覆盖 ufunc 的默认 nditer 标志.
在 API 版本 0x0000000D 中添加
-
npy_intp *core_dim_sizes#
对于每个不同的核心维度,如果
UFUNC_CORE_DIM_SIZE_INFERRED
是0
,则可能的 frozen 大小
-
npy_uint32 *core_dim_flags#
对于每个不同的核心维度,有一组标志(
UFUNC_CORE_DIM_CAN_IGNORE
和UFUNC_CORE_DIM_SIZE_INFERRED
)
-
PyObject *identity_value#
归约的标识,当
PyUFuncObject.identity
等于PyUFunc_IdentityValue
时.
-
UFUNC_CORE_DIM_CAN_IGNORE#
如果维度名称以
?
结尾
PyArrayIter_Type 和 PyArrayIterObject#
-
PyTypeObject PyArrayIter_Type#
这是一个迭代器对象,使得循环遍历一个N维数组变得容易.它是从ndarray的flat属性返回的对象.它也在实现内部被广泛使用,用于循环遍历一个N维数组.实现了tp_as_mapping接口,以便迭代器对象可以被索引(使用1维索引),并且通过tp_methods表实现了一些方法.这个对象实现了next方法,可以在任何可以使用迭代器的地方使用.
-
type PyArrayIterObject#
对应于
PyArrayIter_Type
对象的 C 结构是PyArrayIterObject
.:c:type:PyArrayIterObject 用于跟踪指向 N 维数组的指针.它包含用于快速遍历数组的相关信息.指针可以通过三种基本方式进行调整:1) 以 C 风格的连续方式前进到数组中的”下一个”位置,2) 前进到数组中任意 N 维坐标,3) 前进到数组中任意一维索引.:c:type: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\) 是基础数组中的维度数.
-
PyArrayObject *ao#
指向创建此迭代器以表示的基础 ndarray 的指针.
-
char *dataptr#
此成员指向由索引指示的 ndarray 中的一个元素.
-
npy_bool contiguous#
如果底层数组是
NPY_ARRAY_C_CONTIGUOUS
,则此标志为真.它用于在可能的情况下简化计算.
-
int nd_m1#
如何在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#
需要广播到相同形状的数组数量.
-
int nd#
广播结果中的维度数量.
-
PyArrayIterObject **iters#
一个包含迭代器对象的数组,该数组持有要一起广播的数组的迭代器.返回时,迭代器会调整为广播.
-
int numiter#
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#
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;
成员是
-
void *ptr#
指向内存单段块起始位置的指针.
-
int flags#
任何用于解释内存的数据标志(例如
NPY_ARRAY_WRITEABLE
).
-
void *ptr#
PyArrayInterface#
参见
数组.接口
-
type PyArrayInterface#
该
PyArrayInterface
结构体被定义,以便 NumPy 和其他扩展模块可以使用快速数组接口协议.支持快速数组接口协议的对象的__array_struct__
方法应返回一个包含指向PyArrayInterface
结构体指针的PyCapsule
,该结构体包含数组的相关细节.在新数组创建后,应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’ -> 统一码,’V’ -> 空.
-
int itemsize#
数组中每个项目所需的字节数.
-
int flags#
任何这些位
NPY_ARRAY_C_CONTIGUOUS
(1),NPY_ARRAY_F_CONTIGUOUS
(2),NPY_ARRAY_ALIGNED
(0x100),NPY_ARRAY_NOTSWAPPED
(0x200), 或NPY_ARRAY_WRITEABLE
(0x400) 用于指示数据的一些信息.:c:data:NPY_ARRAY_ALIGNED,NPY_ARRAY_C_CONTIGUOUS
, 和NPY_ARRAY_F_CONTIGUOUS
标志实际上可以从其他参数确定.标志NPY_ARR_HAS_DESCR
(0x800) 也可以设置,以指示使用版本 3 数组接口的对象结构中的 descr 成员存在(它将被使用版本 2 数组接口的对象忽略).
-
void *data#
指向数组第一个元素的指针.
-
PyObject *descr#
一个描述数据类型详细信息的Python对象(与
__array_interface__
中的 descr 键相同).如果 typekind 和 itemsize 提供足够的信息,这可以是NULL
.除非 flags 中的NPY_ARR_HAS_DESCR
标志开启,否则此字段将被忽略.
-
int two#
内部使用的结构#
在内部,代码使用了一些额外的 Python 对象,主要用于内存管理.这些类型不能直接从 Python 访问,也不会暴露给 C-API.这里仅为了完整性和帮助理解代码而包含它们.
-
type PyUFunc_Loop1d#
一个包含定义用户定义数据类型每种定义签名的ufunc一维循环所需信息的C结构简单链表.
-
PyTypeObject PyArrayMapIter_Type#
高级索引是通过这种 Python 类型处理的.它只是围绕包含高级数组索引所需变量的 C 结构的一个松散包装.
-
type PyArrayMapIterObject#
PyArrayMapIter_Type
相关的 C 结构.如果你想理解高级索引映射代码,这个结构非常有用.它在arrayobject.h
头文件中定义.这种类型不暴露给 Python,可以用 C 结构替换.作为 Python 类型,它利用了引用计数的内存管理.
NumPy C-API 和 C 复杂#
当你使用 NumPy C-API 时,你将可以访问复杂的实数声明 npy_cdouble
和 npy_cfloat
,它们是根据 complex.h
中的 C 标准类型声明的.不幸的是,``complex.h`` 包含 #define I …``(其中实际定义取决于编译器),这意味着任何下游用户如果 ``#include <numpy/arrayobject.h>` 可能会得到 I
的定义,并且在他们的代码中使用类似声明 double I;
会导致一个晦涩的编译器错误,如
可以通过添加::来避免此错误.
#undef I
到你的代码.
在 2.0 版本发生变更: 在 NumPy 2 中包含了 complex.h
是一个新内容,因此定义不同 I
的代码在旧版本中可能不需要 #undef I
.NumPy 2.0.1 短暂地包含了 #undef I