NumPy 源码解析(五十九)
.\numpy\numpy\_core\src\multiarray\array_coercion.h
/*
* We do not want to coerce arrays many times unless absolutely necessary.
* The same goes for sequences, so everything we have seen, we will have
* to store somehow. This is a linked list of these objects.
*/
// 定义一个结构体,用于缓存强制转换的对象和数组或序列的信息
typedef struct coercion_cache_obj {
PyObject *converted_obj; // 转换后的对象
PyObject *arr_or_sequence; // 对应的数组或序列对象
struct coercion_cache_obj *next; // 指向下一个缓存对象的指针
npy_bool sequence; // 表示是否为序列
int depth; /* the dimension at which this object was found. */ // 对象发现时的维度
} coercion_cache_obj;
// 将 Python 类型映射为 DType,返回对应的值
NPY_NO_EXPORT int
_PyArray_MapPyTypeToDType(
PyArray_DTypeMeta *DType, PyTypeObject *pytype, npy_bool userdef);
// 从标量类型的 Python 类型中发现对应的 DType
NPY_NO_EXPORT PyObject *
PyArray_DiscoverDTypeFromScalarType(PyTypeObject *pytype);
// 将原始的标量项从一个描述符类型转换为另一个描述符类型
NPY_NO_EXPORT int
npy_cast_raw_scalar_item(
PyArray_Descr *from_descr, char *from_item,
PyArray_Descr *to_descr, char *to_item);
// 将 Python 对象封装为数组描述符对应的值
NPY_NO_EXPORT int
PyArray_Pack(PyArray_Descr *descr, void *item, PyObject *value);
// 根据数组对象和指定的 DTypeMeta,适配数组描述符
NPY_NO_EXPORT PyArray_Descr *
PyArray_AdaptDescriptorToArray(
PyArrayObject *arr, PyArray_DTypeMeta *dtype, PyArray_Descr *descr);
// 从 Python 对象中发现数组的 DType 和形状
NPY_NO_EXPORT int
PyArray_DiscoverDTypeAndShape(
PyObject *obj, int max_dims,
npy_intp out_shape[NPY_MAXDIMS],
coercion_cache_obj **coercion_cache,
PyArray_DTypeMeta *fixed_DType, PyArray_Descr *requested_descr,
PyArray_Descr **out_descr, int copy, int *was_copied_by__array__);
// 发现数组参数并返回相应的 Python 对象
NPY_NO_EXPORT PyObject *
_discover_array_parameters(PyObject *NPY_UNUSED(self),
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames);
// 递归释放 coercion_cache_obj 结构体及其子对象
// 释放 coercion_cache_obj 结构体的递归释放函数
NPY_NO_EXPORT void
npy_free_coercion_cache(coercion_cache_obj *first);
// 断开单个缓存项并返回下一个缓存项
NPY_NO_EXPORT coercion_cache_obj *
npy_unlink_coercion_cache(coercion_cache_obj *current);
// 从缓存中将数据分配到数组对象中
NPY_NO_EXPORT int
PyArray_AssignFromCache(PyArrayObject *self, coercion_cache_obj *cache);
.\numpy\numpy\_core\src\multiarray\array_converter.c
/*
* This file defines an _array_converter object used internally in NumPy to
* deal with `__array_wrap__` and `result_type()` for multiple arguments
* where converting inputs to arrays would lose the necessary information.
*
* The helper thus replaces many asanyarray/asarray calls.
*/
// Define a static function to create a new array converter object
static PyObject *
array_converter_new(
PyTypeObject *cls, PyObject *args, PyObject *kwds)
{
// Check if keywords are provided; array creation helper doesn't support keywords
if (kwds != NULL && PyDict_GET_SIZE(kwds) != 0) {
PyErr_SetString(PyExc_TypeError,
"Array creation helper doesn't support keywords.");
return NULL;
}
// Determine the number of arguments passed
Py_ssize_t narrs_ssize_t = (args == NULL) ? 0 : PyTuple_GET_SIZE(args);
int narrs = (int)narrs_ssize_t;
// Limit the number of arguments to NPY_MAXARGS
/* Limit to NPY_MAXARGS for now. */
if (narrs_ssize_t > NPY_MAXARGS) {
PyErr_SetString(PyExc_RuntimeError,
"too many arrays.");
return NULL;
}
// Allocate memory for the PyArrayArrayConverterObject instance
PyArrayArrayConverterObject *self = PyObject_NewVar(
PyArrayArrayConverterObject, cls, narrs);
if (self == NULL) {
return NULL;
}
// Initialize the PyObject instance with PyArrayArrayConverter_Type and narrs
PyObject_InitVar((PyVarObject *)self, &PyArrayArrayConverter_Type, narrs);
// Initialize attributes of the array converter object
self->narrs = 0;
self->flags = 0;
self->wrap = NULL;
self->wrap_type = NULL;
// If no arguments are passed, return the initialized object
if (narrs == 0) {
return (PyObject *)self;
}
// Set flags for the array converter object
self->flags = (NPY_CH_ALL_PYSCALARS | NPY_CH_ALL_SCALARS);
// Initialize creation_item pointer for iterating over items
creation_item *item = self->items;
// Increase self->narrs in loop for cleanup
/* increase self->narrs in loop for cleanup */
for (int i = 0; i < narrs; i++, item++) {
// 将参数元组中的第 i 个对象赋给当前处理的 item 的 object 字段
item->object = PyTuple_GET_ITEM(args, i);
/* Fast path if input is an array (maybe FromAny should be faster): */
// 如果 item->object 是一个 NumPy 数组,则执行快速路径
if (PyArray_Check(item->object)) {
// 增加 item->object 的引用计数
Py_INCREF(item->object);
// 将 item->object 转换为 PyArrayObject,并赋给 item->array
item->array = (PyArrayObject *)item->object;
// 表示 item->object 不是标量输入
item->scalar_input = 0;
}
else {
// 从 item->object 创建 PyArrayObject 对象,尝试转换为 NumPy 数组
item->array = (PyArrayObject *)PyArray_FromAny_int(
item->object, NULL, NULL, 0, 0, 0, NULL,
&item->scalar_input);
// 如果转换失败,跳转到失败标签 fail
if (item->array == NULL) {
goto fail;
}
}
/* At this point, assume cleanup should happen for this item */
// 假设此时应为该 item 执行清理工作
self->narrs++;
// 增加 item->object 的引用计数
Py_INCREF(item->object);
// 获取 item->array 的数据类型,并赋给 item->DType
item->DType = NPY_DTYPE(PyArray_DESCR(item->array));
// 增加 item->DType 的引用计数
Py_INCREF(item->DType);
/*
* Check whether we were passed a an int/float/complex Python scalar.
* If not, set `descr` and clear pyscalar/scalar flags as needed.
*/
// 检查是否传递了 int/float/complex 类型的 Python 标量
if (item->scalar_input && npy_mark_tmp_array_if_pyscalar(
item->object, item->array, &item->DType)) {
// 如果是 Python 标量,则设置 item->descr 为 NULL
item->descr = NULL;
// 不标记存储的数组为 Python 文字量
((PyArrayObject_fields *)(item->array))->flags &= (
~NPY_ARRAY_WAS_PYTHON_LITERAL);
}
else {
// 否则获取 item->array 的描述符,并增加引用计数
item->descr = PyArray_DESCR(item->array);
Py_INCREF(item->descr);
// 如果不是标量输入
if (item->scalar_input) {
// 清除 self 的标量输入标志位
self->flags &= ~NPY_CH_ALL_PYSCALARS;
}
else {
// 清除 self 的标量输入和标量标志位
self->flags &= ~(NPY_CH_ALL_PYSCALARS | NPY_CH_ALL_SCALARS);
}
}
}
// 成功处理完所有项后,返回 self 对象作为 PyObject 指针
return (PyObject *)self;
fail:
// 处理失败时,减少 self 的引用计数并返回 NULL
Py_DECREF(self);
return NULL;
static PyObject *
array_converter_get_scalar_input(PyArrayArrayConverterObject *self)
{
// 创建一个元组,用于存放返回结果,元组长度为 self->narrs
PyObject *ret = PyTuple_New(self->narrs);
if (ret == NULL) {
return NULL;
}
// 遍历 self->items 数组,为每个元素创建一个布尔值对象,表示是否为标量输入
creation_item *item = self->items;
for (int i = 0; i < self->narrs; i++, item++) {
if (item->scalar_input) {
// 如果是标量输入,增加 True 的引用计数,并设置到元组的第 i 个位置
Py_INCREF(Py_True);
PyTuple_SET_ITEM(ret, i, Py_True);
}
else {
// 如果不是标量输入,增加 False 的引用计数,并设置到元组的第 i 个位置
Py_INCREF(Py_False);
PyTuple_SET_ITEM(ret, i, Py_False);
}
}
return ret; // 返回填充好的元组对象
}
static int
find_wrap(PyArrayArrayConverterObject *self)
{
// 如果 self->wrap 已经被设置,直接返回 0,不执行任何操作
if (self->wrap != NULL) {
return 0; /* nothing to do */
}
// 分配临时空间用于存放对象指针,长度为 self->narrs
PyObject **objects = PyMem_Malloc(self->narrs * sizeof(PyObject *));
if (objects == NULL) {
PyErr_NoMemory(); // 内存分配失败,设置内存错误并返回 -1
return -1;
}
// 将 self->items 数组中的 object 指针复制到 objects 数组中
for (int i = 0; i < self->narrs; i++) {
objects[i] = self->items[i].object;
}
// 调用 npy_find_array_wrap 函数查找数组包装器,将结果存储在 self->wrap 和 self->wrap_type 中
int ret = npy_find_array_wrap(
self->narrs, objects, &self->wrap, &self->wrap_type);
PyMem_FREE(objects); // 释放临时对象数组的内存空间
return ret; // 返回 npy_find_array_wrap 函数的执行结果
}
typedef enum {
CONVERT = 0,
PRESERVE = 1,
CONVERT_IF_NO_ARRAY = 2,
} scalar_policy;
static int
pyscalar_mode_conv(PyObject *obj, scalar_policy *policy)
{
// 预先定义三个字符串对象的数组
PyObject *strings[3] = {
npy_interned_str.convert, npy_interned_str.preserve,
npy_interned_str.convert_if_no_array};
// 第一轮快速匹配,通过对象的身份进行比较
for (int i = 0; i < 3; i++) {
if (obj == strings[i]) {
*policy = i; // 匹配成功,设置 policy 的值为 i
return 1; // 返回匹配成功的标志
}
}
// 第二轮比较,通过 PyObject_RichCompareBool 函数进行比较
for (int i = 0; i < 3; i++) {
int cmp = PyObject_RichCompareBool(obj, strings[i], Py_EQ);
if (cmp < 0) {
return 0; // 比较失败,返回错误标志
}
if (cmp) {
*policy = i; // 匹配成功,设置 policy 的值为 i
return 1; // 返回匹配成功的标志
}
}
// 如果没有匹配成功,则设置 ValueError 异常并返回错误标志
PyErr_SetString(PyExc_ValueError,
"invalid pyscalar mode, must be 'convert', 'preserve', or "
"'convert_if_no_array' (default).");
return 0; // 返回匹配失败的标志
}
static PyObject *
array_converter_as_arrays(PyArrayArrayConverterObject *self,
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
// 设置默认的 subok 和 policy 值
npy_bool subok = NPY_TRUE;
scalar_policy policy = CONVERT_IF_NO_ARRAY;
// 解析函数参数,其中 "as_arrays" 是函数名
NPY_PREPARE_ARGPARSER;
if (npy_parse_arguments("as_arrays", args, len_args, kwnames,
"$subok", &PyArray_BoolConverter, &subok,
/* how to handle scalars (ignored if dtype is given). */
"$pyscalars", &pyscalar_mode_conv, &policy,
NULL, NULL, NULL) < 0) {
return NULL; // 解析参数失败,返回 NULL
}
// 如果 policy 为 CONVERT_IF_NO_ARRAY,则根据 self->flags 的设置调整 policy 的值
if (policy == CONVERT_IF_NO_ARRAY) {
if (self->flags & NPY_CH_ALL_PYSCALARS) {
policy = CONVERT;
}
else {
policy = PRESERVE;
}
}
// 创建一个元组用于存放返回结果,长度为 self->narrs
PyObject *res = PyTuple_New(self->narrs);
if (res == NULL) {
return NULL; // 创建元组失败,返回 NULL
}
// 获取 self->items 数组的首地址
creation_item *item = self->items;
// 继续函数实现的下一步操作...
// 循环遍历 self 对象的 narrs 个数
for (int i = 0; i < self->narrs; i++, item++) {
// 定义结果项指针 res_item
PyObject *res_item;
// 如果 item 的描述符为 NULL 并且策略为 PRESERVE
if (item->descr == NULL && policy == PRESERVE) {
// 将 res_item 指向 item 的对象,并增加其引用计数
res_item = item->object;
Py_INCREF(res_item);
}
// 否则
else {
// 将 res_item 指向 item 的数组对象,并增加其引用计数
res_item = (PyObject *)item->array;
Py_INCREF(res_item);
// 如果 subok 参数为假
if (!subok) {
/* PyArray_EnsureArray steals the reference... */
// 调用 PyArray_EnsureArray 确保 res_item 是数组对象
res_item = PyArray_EnsureArray(res_item);
// 如果返回值为 NULL,则跳转到 fail 标签
if (res_item == NULL) {
goto fail;
}
}
}
// 将 res_item 添加到元组 res 的第 i 个位置
if (PyTuple_SetItem(res, i, res_item) < 0) {
// 如果添加失败,则跳转到 fail 标签
goto fail;
}
}
// 成功返回结果元组 res
return res;
fail:
// 出错时,减少结果元组 res 的引用计数
Py_DECREF(res);
// 返回 NULL 指针
return NULL;
}
static PyObject *
array_converter_wrap(PyArrayArrayConverterObject *self,
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
PyObject *obj;
PyObject *to_scalar = Py_None;
npy_bool ensure_scalar;
// 检查是否已经初始化包装器,若未初始化则返回 NULL
if (find_wrap(self) < 0) {
return NULL;
}
NPY_PREPARE_ARGPARSER;
// 解析参数,支持额外参数 $to_scalar 作为布尔值,若为 None 则由输入对象决定
if (npy_parse_arguments("wrap", args, len_args, kwnames,
"", NULL, &obj,
/* Three-way "bool", if `None` inspect input to decide. */
"$to_scalar", NULL, &to_scalar,
NULL, NULL, NULL) < 0) {
return NULL;
}
// 根据 $to_scalar 决定是否确保对象为标量
if (to_scalar == Py_None) {
ensure_scalar = self->flags & NPY_CH_ALL_SCALARS;
}
else {
// 将 $to_scalar 转换为布尔值,若转换失败则返回 NULL
if (!PyArray_BoolConverter(to_scalar, &ensure_scalar)) {
return NULL;
}
}
// 应用包装操作并返回结果
return npy_apply_wrap(
obj, NULL, self->wrap, self->wrap_type, NULL, ensure_scalar, NPY_FALSE);
}
static PyObject *
array_converter_result_type(PyArrayArrayConverterObject *self,
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
PyArray_Descr *result = NULL;
npy_dtype_info dt_info = {NULL, NULL};
npy_bool ensure_inexact = NPY_FALSE;
/* 分配临时空间(可以优化掉) */
void *DTypes_and_descrs = PyMem_Malloc(
((self->narrs + 1) * 2) * sizeof(PyObject *));
if (DTypes_and_descrs == NULL) {
PyErr_NoMemory();
return NULL;
}
PyArray_DTypeMeta **DTypes = DTypes_and_descrs;
PyArray_Descr **descrs = (PyArray_Descr **)(DTypes + self->narrs + 1);
NPY_PREPARE_ARGPARSER;
// 解析参数,支持可选参数 "extra_dtype" 和 "ensure_inexact"
if (npy_parse_arguments("result_type", args, len_args, kwnames,
"|extra_dtype", &PyArray_DTypeOrDescrConverterOptional, &dt_info,
"|ensure_inexact", &PyArray_BoolConverter, &ensure_inexact,
NULL, NULL, NULL) < 0) {
goto finish;
}
int ndescrs = 0;
int nDTypes = 0;
creation_item *item = self->items;
// 遍历项目列表,收集数据类型和描述符
for (int i = 0; i < self->narrs; i++, item++) {
DTypes[nDTypes] = item->DType;
nDTypes++;
if (item->descr != NULL) {
descrs[ndescrs] = item->descr;
ndescrs++;
}
}
// 若需要确保结果为非精确数,则设置默认浮点数类型
if (ensure_inexact) {
if (dt_info.dtype != NULL) {
PyErr_SetString(PyExc_TypeError,
"extra_dtype and ensure_inexact are mutually exclusive.");
goto finish;
}
Py_INCREF(&PyArray_PyFloatDType);
dt_info.dtype = &PyArray_PyFloatDType;
}
// 添加额外的数据类型和描述符
if (dt_info.dtype != NULL) {
DTypes[nDTypes] = dt_info.dtype;
nDTypes++;
}
if (dt_info.descr != NULL) {
descrs[ndescrs] = dt_info.descr;
ndescrs++;
}
// 推断通用数据类型
PyArray_DTypeMeta *common_dtype = PyArray_PromoteDTypeSequence(
nDTypes, DTypes);
if (common_dtype == NULL) {
goto finish;
}
// 若无描述符,则调用默认描述符生成函数
if (ndescrs == 0) {
result = NPY_DT_CALL_default_descr(common_dtype);
}
else {
// 否则,调用 PyArray_CastToDTypeAndPromoteDescriptors 函数进行类型转换和描述符提升
result = PyArray_CastToDTypeAndPromoteDescriptors(
ndescrs, descrs, common_dtype);
}
// 释放 common_dtype 对象的引用计数
Py_DECREF(common_dtype);
finish:
// 释放 dt_info.descr 对象的引用计数
Py_XDECREF(dt_info.descr);
// 释放 dt_info.dtype 对象的引用计数
Py_XDECREF(dt_info.dtype);
// 释放 DTypes_and_descrs 所占用的内存
PyMem_Free(DTypes_and_descrs);
// 返回 result 对象作为 PyObject 指针类型的结果
return (PyObject *)result;
# 定义一个静态的属性获取器和设置器数组,用于Python对象的特性获取和设置
static PyGetSetDef array_converter_getsets[] = {
{"scalar_input",
(getter)array_converter_get_scalar_input, # 获取标量输入的函数指针
NULL, # 没有设置器,因此设为NULL
NULL, NULL}, # 获取器和设置器的文档字符串为空
{NULL, NULL, NULL, NULL, NULL}, # 结束符号,用于指示属性列表的结束
};
# 定义一个静态的方法数组,用于Python对象的方法定义
static PyMethodDef array_converter_methods[] = {
{"as_arrays",
(PyCFunction)array_converter_as_arrays, # 指向转换为数组的函数指针
METH_FASTCALL | METH_KEYWORDS, NULL}, # 使用快速调用和关键字参数的方法标志,文档字符串为空
{"result_type",
(PyCFunction)array_converter_result_type, # 指向结果类型函数的函数指针
METH_FASTCALL | METH_KEYWORDS, NULL}, # 使用快速调用和关键字参数的方法标志,文档字符串为空
{"wrap",
(PyCFunction)array_converter_wrap, # 指向包装函数的函数指针
METH_FASTCALL | METH_KEYWORDS, NULL}, # 使用快速调用和关键字参数的方法标志,文档字符串为空
{NULL, NULL, 0, NULL} # 结束符号,用于指示方法列表的结束
};
# 定义一个静态的析构函数,用于释放内存和资源
static void
array_converter_dealloc(PyArrayArrayConverterObject *self)
{
creation_item *item = self->items; # 初始化创建项指针,指向self对象的items数组
for (int i = 0; i < self->narrs; i++, item++) {
Py_XDECREF(item->array); # 递减引用计数并释放数组对象
Py_XDECREF(item->object); # 递减引用计数并释放Python对象
Py_XDECREF(item->DType); # 递减引用计数并释放数据类型对象
Py_XDECREF(item->descr); # 递减引用计数并释放描述符对象
}
Py_XDECREF(self->wrap); # 递减引用计数并释放wrap对象
Py_XDECREF(self->wrap_type); # 递减引用计数并释放wrap_type对象
PyObject_Del((PyObject *)self); # 删除Python对象self并释放其内存
}
# 定义一个长度函数,返回PyArrayArrayConverterObject对象的长度
static Py_ssize_t
array_converter_length(PyArrayArrayConverterObject *self)
{
return self->narrs; # 返回对象self的narrs属性,即数组的数量
}
# 定义一个项目获取函数,返回PyArrayArrayConverterObject对象的第item项
static PyObject *
array_converter_item(PyArrayArrayConverterObject *self, Py_ssize_t item)
{
/* Python ensures no negative indices (and probably the below also) */
# Python确保没有负索引(也可能下面的操作也是如此)
if (item < 0 || item >= self->narrs) {
PyErr_SetString(PyExc_IndexError, "invalid index"); # 设置索引错误异常并返回NULL
return NULL;
}
/* Follow the `as_arrays` default of `CONVERT_IF_NO_ARRAY`: */
# 遵循`as_arrays`的默认设置`CONVERT_IF_NO_ARRAY`
PyObject *res;
if (self->items[item].descr == NULL
&& !(self->flags & NPY_CH_ALL_PYSCALARS)) {
res = self->items[item].object; # 如果描述符为NULL且标志中不包含所有Python标量,则结果为对象
}
else {
res = (PyObject *)self->items[item].array; # 否则结果为数组对象的PyObject指针转换
}
Py_INCREF(res); # 增加结果对象的引用计数
return res; # 返回结果对象
}
# 定义一个序列方法结构体,包含了长度函数和项目获取函数
static PySequenceMethods array_converter_as_sequence = {
.sq_length = (lenfunc)array_converter_length, # 序列长度函数指针
.sq_item = (ssizeargfunc)array_converter_item, # 序列项目获取函数指针
};
# 定义一个PyTypeObject对象,描述PyArrayArrayConverterObject对象的类型信息
NPY_NO_EXPORT PyTypeObject PyArrayArrayConverter_Type = {
PyVarObject_HEAD_INIT(NULL, 0) # 初始化Python对象头部信息
.tp_name = "numpy._core._multiarray_umath._array_converter", # 类型对象的名称
.tp_basicsize = sizeof(PyArrayArrayConverterObject), # 类型对象的基本大小
.tp_itemsize = sizeof(creation_item), # 类型对象的每个项的大小
.tp_new = array_converter_new, # 类型对象的构造函数
.tp_dealloc = (destructor)array_converter_dealloc, # 类型对象的析构函数
.tp_flags = Py_TPFLAGS_DEFAULT, # 类型对象的标志
.tp_getset = array_converter_getsets, # 类型对象的属性获取器和设置器数组
.tp_methods = array_converter_methods, # 类型对象的方法数组
.tp_as_sequence = &array_converter_as_sequence, # 类型对象的序列方法结构体
};
.\numpy\numpy\_core\src\multiarray\array_converter.h
// 如果未定义 NUMPY_CORE_SRC_MULTIARRAY_ARRAY_CONVERTER_H_ 宏,则开始条件编译
// 包含 ndarraytypes.h 文件,定义了与 ndarray 相关的类型和宏
// 声明 PyArrayArrayConverter_Type 变量,不导出给外部
extern NPY_NO_EXPORT PyTypeObject PyArrayArrayConverter_Type;
// 定义枚举类型 npy_array_converter_flags,用于标志数组转换器的特性
typedef enum {
NPY_CH_ALL_SCALARS = 1 << 0, // 表示所有对象都是标量
NPY_CH_ALL_PYSCALARS = 1 << 1, // 表示所有对象都是 Python 标量对象
} npy_array_converter_flags;
// 定义结构体 creation_item,描述创建对象的元数据
typedef struct {
PyObject *object; // Python 对象
PyArrayObject *array; // NumPy 数组对象
PyArray_DTypeMeta *DType; // NumPy 数据类型元信息
PyArray_Descr *descr; // NumPy 数据描述符
int scalar_input; // 是否标量输入的标志
} creation_item;
// 定义结构体 PyArrayArrayConverterObject,表示数组转换器对象
typedef struct {
PyObject_VAR_HEAD // 可变大小对象的头部
int narrs; // 数组的数量
npy_array_converter_flags flags; // 数组转换器的特性标志
PyObject *wrap; // __array_wrap__ 缓存对象
PyObject *wrap_type; // __array_wrap__ 方法的类型
creation_item items[]; // 创建对象的元数据数组
} PyArrayArrayConverterObject;
.\numpy\numpy\_core\src\multiarray\array_method.c
/*
* This file implements an abstraction layer for "Array methods", which
* work with a specific DType class input and provide low-level C function
* pointers to do fast operations on the given input functions.
* It thus adds an abstraction layer around individual ufunc loops.
*
* Unlike methods, a ArrayMethod can have multiple inputs and outputs.
* This has some serious implication for garbage collection, and as far
* as I (@seberg) understands, it is not possible to always guarantee correct
* cyclic garbage collection of dynamically created DTypes with methods.
* The keyword (or rather the solution) for this seems to be an "ephemeron"
* which I believe should allow correct garbage collection but seems
* not implemented in Python at this time.
* The vast majority of use-cases will not require correct garbage collection.
* Some use cases may require the user to be careful.
*
* Generally there are two main ways to solve this issue:
*
* 1. A method with a single input (or inputs of all the same DTypes) can
* be "owned" by that DType (it becomes unusable when the DType is deleted).
* This holds especially for all casts, which must have a defined output
* DType and must hold on to it strongly.
* 2. A method which can infer the output DType(s) from the input types does
* not need to keep the output type alive. (It can use NULL for the type,
* or an abstract base class which is known to be persistent.)
* It is then sufficient for a ufunc (or other owner) to only hold a
* weak reference to the input DTypes.
*/
/*
* The default descriptor resolution function. The logic is as follows:
*
* 1. The output is ensured to be canonical (currently native byte order),
* if it is of the correct DType.
* 2. If any DType is was not defined, it is replaced by the common DType
* of all inputs. (If that common DType is parametric, this is an error.)
*
* We could allow setting the output descriptors specifically to simplify
* this step.
*
* Note that the default version will indicate that the cast can be done
* as using `arr.view(new_dtype)` if the default cast-safety is
* set to "no-cast". This default function cannot be used if a view may
* be sufficient for casting but the cast is not always "no-cast".
*/
static NPY_CASTING
default_resolve_descriptors(
PyArrayMethodObject *method,
PyArray_DTypeMeta *const *dtypes,
PyArray_Descr *const *input_descrs,
PyArray_Descr **output_descrs,
npy_intp *view_offset)
{
int nin = method->nin; // Number of input arguments for the method
int nout = method->nout; // Number of output arguments for the method
// 遍历输入输出数组的所有元素
for (int i = 0; i < nin + nout; i++) {
// 获取当前元素对应的数据类型
PyArray_DTypeMeta *dtype = dtypes[i];
// 如果输入描述符不为空,将输出描述符设为规范化后的输入描述符
if (input_descrs[i] != NULL) {
output_descrs[i] = NPY_DT_CALL_ensure_canonical(input_descrs[i]);
}
// 否则,使用默认数据类型描述符
else {
output_descrs[i] = NPY_DT_CALL_default_descr(dtype);
}
// 如果输出描述符为空,跳转到失败处理标签
if (NPY_UNLIKELY(output_descrs[i] == NULL)) {
goto fail;
}
}
/*
* 如果方法的类型转换设置为 NPY_NO_CASTING,
* 则假设所有的类型转换都不需要,视为可视的情况下。
*/
if (method->casting == NPY_NO_CASTING) {
/*
* 根据当前定义,无类型转换应该意味着可视化。
* 例如,对象到对象的转换就会标记为可视化。
*/
*view_offset = 0;
}
// 返回当前方法的类型转换设置
return method->casting;
fail:
// 失败时释放已分配的输出描述符内存
for (int i = 0; i < nin + nout; i++) {
Py_XDECREF(output_descrs[i]);
}
// 返回失败状态
return -1;
/**
* Check if the given strides match the element sizes of the descriptors,
* indicating contiguous memory layout for each argument.
*
* @param strides An array of strides for each argument.
* @param descriptors Array of descriptors for each argument.
* @param nargs Number of arguments (descriptors and strides).
* @return 1 if all strides match descriptors' element sizes, otherwise 0.
*/
static inline int
is_contiguous(
npy_intp const *strides, PyArray_Descr *const *descriptors, int nargs)
{
// Iterate through each argument
for (int i = 0; i < nargs; i++) {
// Check if the stride of the argument matches its descriptor's element size
if (strides[i] != descriptors[i]->elsize) {
return 0; // Not contiguous if stride doesn't match element size
}
}
return 1; // All strides match descriptors' element sizes, indicating contiguous memory
}
/**
* The default method to fetch the correct loop for a cast or ufunc
* (at the time of writing only casts).
* Note that the default function provided here will only indicate that a cast
* can be done as a view (i.e., arr.view(new_dtype)) when this is trivially
* true, i.e., for cast safety "no-cast". It will not recognize view as an
* option for other casts (e.g., viewing '>i8' as '>i4' with an offset of 4).
*
* @param context The context object containing method descriptors and flags.
* @param aligned Flag indicating if memory should be aligned.
* @param move_references UNUSED (currently not used in the function).
* @param strides An array of strides for each argument.
* @param descriptors Array of descriptors for each argument.
* @param out_loop Pointer to store the selected strided loop.
* @param out_transferdata Unused in this function, set to NULL.
* @param flags Pointer to store method flags.
* @return 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
npy_default_get_strided_loop(
PyArrayMethod_Context *context,
int aligned, int NPY_UNUSED(move_references), const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
{
PyArray_Descr *const *descrs = context->descriptors; // Array of descriptors
PyArrayMethodObject *meth = context->method; // Method object from context
*flags = meth->flags & NPY_METH_RUNTIME_FLAGS; // Set method flags
int nargs = meth->nin + meth->nout; // Total number of arguments
// Determine which loop to select based on alignment
if (aligned) {
// Use contiguous loop if available and if arguments are contiguous
if (meth->contiguous_loop == NULL ||
!is_contiguous(strides, descrs, nargs)) {
*out_loop = meth->strided_loop;
return 0;
}
*out_loop = meth->contiguous_loop;
}
else {
// Use unaligned strided loop if available and if arguments are contiguous
if (meth->unaligned_contiguous_loop == NULL ||
!is_contiguous(strides, descrs, nargs)) {
*out_loop = meth->unaligned_strided_loop;
return 0;
}
*out_loop = meth->unaligned_contiguous_loop;
}
return 0; // Success
}
/**
* Validate that the input specification is usable to create a new ArrayMethod.
*
* @param spec The specification object to validate.
* @return 0 on success, -1 on error.
*/
static int
validate_spec(PyArrayMethod_Spec *spec)
{
int nargs = spec->nin + spec->nout; // Total number of arguments
// Check for invalid input specification fields/values
if (spec->nin < 0 || spec->nout < 0 || nargs > NPY_MAXARGS) {
PyErr_Format(PyExc_ValueError,
"ArrayMethod inputs and outputs must be greater zero and"
"not exceed %d. (method: %s)", NPY_MAXARGS, spec->name);
return -1; // Return error if inputs/outputs are invalid
}
// Check for valid casting options
switch (spec->casting) {
case NPY_NO_CASTING:
case NPY_EQUIV_CASTING:
case NPY_SAFE_CASTING:
case NPY_SAME_KIND_CASTING:
case NPY_UNSAFE_CASTING:
break; // Valid casting types
default:
if (spec->casting != -1) {
PyErr_Format(PyExc_TypeError,
"ArrayMethod has invalid casting `%d`. (method: %s)",
spec->casting, spec->name);
return -1; // Return error for invalid casting type
}
}
// Input specification is valid
return 0;
}
for (int i = 0; i < nargs; i++) {
/*
* 注意,我们可以允许输出数据类型未指定
* (数组方法必须确保支持此功能)。
* 我们甚至可以允许某些数据类型是抽象的。
* 目前,假设这最好在提升步骤中处理。
* 提供所有数据类型的一个问题是明确需要保持引用。
* 我们可能最终需要实现遍历并信任 GC 处理它。
*/
// 检查是否存在未指定的输出数据类型
if (spec->dtypes[i] == NULL) {
// 报错:ArrayMethod 必须提供所有输入和输出的数据类型。
PyErr_Format(PyExc_TypeError,
"ArrayMethod must provide all input and output DTypes. "
"(method: %s)", spec->name);
return -1;
}
// 检查提供的数据类型对象是否为 DType 类型
if (!PyObject_TypeCheck(spec->dtypes[i], &PyArrayDTypeMeta_Type)) {
// 报错:提供的对象不是 DType 类型。
PyErr_Format(PyExc_TypeError,
"ArrayMethod provided object %R is not a DType."
"(method: %s)", spec->dtypes[i], spec->name);
return -1;
}
}
// 所有检查通过,返回成功状态
return 0;
/**
* Initialize a new BoundArrayMethodObject from slots. Slots which are
* not provided may be filled with defaults.
*
* @param res The new PyBoundArrayMethodObject to be filled.
* @param spec The specification list passed by the user.
* @param private Private flag to limit certain slots to use in NumPy.
* @return -1 on error 0 on success
*/
static int
fill_arraymethod_from_slots(
PyBoundArrayMethodObject *res, PyArrayMethod_Spec *spec,
int private)
{
// 获取方法对象
PyArrayMethodObject *meth = res->method;
/* Set the defaults */
// 设置默认的 strided loop 获取函数为 npy_default_get_strided_loop
meth->get_strided_loop = &npy_default_get_strided_loop;
// 设置默认的解析描述符函数为 default_resolve_descriptors
meth->resolve_descriptors = &default_resolve_descriptors;
// 默认没有初始值或者标识
meth->get_reduction_initial = NULL; /* no initial/identity by default */
/* Fill in the slots passed by the user */
/*
* TODO: This is reasonable for now, but it would be nice to find a
* shorter solution, and add some additional error checking (e.g.
* the same slot used twice). Python uses an array of slot offsets.
*/
for (PyType_Slot *slot = &spec->slots[0]; slot->slot != 0; slot++) {
switch (slot->slot) {
case _NPY_METH_resolve_descriptors_with_scalars:
if (!private) {
PyErr_SetString(PyExc_RuntimeError,
"the _NPY_METH_resolve_descriptors_with_scalars "
"slot private due to uncertainty about the best "
"signature (see gh-24915)");
return -1;
}
meth->resolve_descriptors_with_scalars = slot->pfunc;
continue;
case NPY_METH_resolve_descriptors:
meth->resolve_descriptors = slot->pfunc;
continue;
case NPY_METH_get_loop:
"""
* 注意:公共API中的get_loop被认为是“不稳定的”,
* 我不喜欢它的签名,并且不应使用move_references参数。
* (也就是说:我们不应该担心立即更改它,当然这并不会立即中断它。)
"""
meth->get_strided_loop = slot->pfunc;
continue;
case NPY_METH_strided_loop:
meth->strided_loop = slot->pfunc;
continue;
case NPY_METH_contiguous_loop:
meth->contiguous_loop = slot->pfunc;
continue;
case NPY_METH_unaligned_strided_loop:
meth->unaligned_strided_loop = slot->pfunc;
continue;
case NPY_METH_unaligned_contiguous_loop:
meth->unaligned_contiguous_loop = slot->pfunc;
continue;
case NPY_METH_get_reduction_initial:
meth->get_reduction_initial = slot->pfunc;
continue;
case NPY_METH_contiguous_indexed_loop:
meth->contiguous_indexed_loop = slot->pfunc;
continue;
case _NPY_METH_static_data:
meth->static_data = slot->pfunc;
continue;
default:
break;
}
PyErr_Format(PyExc_RuntimeError,
"invalid slot number %d to ArrayMethod: %s",
slot->slot, spec->name);
return -1;
}
// 检查是否使用默认的解析描述符函数
if (meth->resolve_descriptors == &default_resolve_descriptors) {
// 如果设置了casting为-1但未提供默认的`resolve_descriptors`函数,则抛出TypeError异常
if (spec->casting == -1) {
PyErr_Format(PyExc_TypeError,
"Cannot set casting to -1 (invalid) when not providing "
"the default `resolve_descriptors` function. "
"(method: %s)", spec->name);
return -1;
}
// 遍历输入和输出的数据类型列表
for (int i = 0; i < meth->nin + meth->nout; i++) {
// 如果某个数据类型为NULL
if (res->dtypes[i] == NULL) {
// 如果是输入数据类型并且使用了默认的`resolve_descriptors`函数,则抛出TypeError异常
if (i < meth->nin) {
PyErr_Format(PyExc_TypeError,
"All input DTypes must be specified when using "
"the default `resolve_descriptors` function. "
"(method: %s)", spec->name);
return -1;
}
// 如果没有输入数据并且未指定输出数据类型或使用自定义的`resolve_descriptors`函数,则抛出TypeError异常
else if (meth->nin == 0) {
PyErr_Format(PyExc_TypeError,
"Must specify output DTypes or use custom "
"`resolve_descriptors` when there are no inputs. "
"(method: %s)", spec->name);
return -1;
}
}
// 如果是输出数据类型且为参数化数据类型,则必须提供`resolve_descriptors`函数,否则抛出TypeError异常
if (i >= meth->nin && NPY_DT_is_parametric(res->dtypes[i])) {
PyErr_Format(PyExc_TypeError,
"must provide a `resolve_descriptors` function if any "
"output DType is parametric. (method: %s)",
spec->name);
return -1;
}
}
}
// 如果不是使用默认的获取步进循环函数,则返回0
if (meth->get_strided_loop != &npy_default_get_strided_loop) {
/* Do not check the actual loop fields. */
return 0;
}
/* Check whether the provided loops make sense. */
// 如果方法标志包含NPY_METH_SUPPORTS_UNALIGNED
if (meth->flags & NPY_METH_SUPPORTS_UNALIGNED) {
// 如果未提供非对齐步进内部循环,则抛出TypeError异常
if (meth->unaligned_strided_loop == NULL) {
PyErr_Format(PyExc_TypeError,
"Must provide unaligned strided inner loop when using "
"NPY_METH_SUPPORTS_UNALIGNED flag (in method: %s)",
spec->name);
return -1;
}
}
// 如果方法标志不包含NPY_METH_SUPPORTS_UNALIGNED
else {
// 如果提供了非对齐步进内部循环,则抛出TypeError异常
if (meth->unaligned_strided_loop != NULL) {
PyErr_Format(PyExc_TypeError,
"Must not provide unaligned strided inner loop when not "
"using NPY_METH_SUPPORTS_UNALIGNED flag (in method: %s)",
spec->name);
return -1;
}
}
/* Fill in the blanks: */
// 如果未提供非对齐连续循环,则使用非对齐步进内部循环
if (meth->unaligned_contiguous_loop == NULL) {
meth->unaligned_contiguous_loop = meth->unaligned_strided_loop;
}
// 如果未提供步进循环,则使用非对齐步进内部循环
if (meth->strided_loop == NULL) {
meth->strided_loop = meth->unaligned_strided_loop;
}
// 如果未提供连续循环,则使用步进循环
if (meth->contiguous_loop == NULL) {
meth->contiguous_loop = meth->strided_loop;
}
if (meth->strided_loop == NULL) {
PyErr_Format(PyExc_TypeError,
"Must provide a strided inner loop function. (method: %s)",
spec->name);
return -1;
}
return 0;
/*
* Public version of `PyArrayMethod_FromSpec_int` (see below).
*
* TODO: Error paths will probably need to be improved before a release into
* the non-experimental public API.
*/
NPY_NO_EXPORT PyObject *
PyArrayMethod_FromSpec(PyArrayMethod_Spec *spec)
{
// 遍历输入输出类型列表,确保每个类型都是 PyArrayDTypeMeta_Type 的实例
for (int i = 0; i < spec->nin + spec->nout; i++) {
if (!PyObject_TypeCheck(spec->dtypes[i], &PyArrayDTypeMeta_Type)) {
PyErr_SetString(PyExc_RuntimeError,
"ArrayMethod spec contained a non DType.");
return NULL;
}
}
// 调用内部函数 PyArrayMethod_FromSpec_int 处理具体逻辑,返回处理结果
return (PyObject *)PyArrayMethod_FromSpec_int(spec, 0);
}
/**
* Create a new ArrayMethod (internal version).
*
* @param spec A filled context object to pass generic information about
* the method (such as usually needing the API, and the DTypes).
* Unused fields must be NULL.
* @param private Some slots are currently considered private, if not true,
* these will be rejected.
*
* @returns A new (bound) ArrayMethod object.
*/
NPY_NO_EXPORT PyBoundArrayMethodObject *
PyArrayMethod_FromSpec_int(PyArrayMethod_Spec *spec, int private)
{
int nargs = spec->nin + spec->nout;
// 如果方法名为 NULL,则设置为 "<unknown>"
if (spec->name == NULL) {
spec->name = "<unknown>";
}
// 验证 spec 结构是否有效,若无效则返回 NULL
if (validate_spec(spec) < 0) {
return NULL;
}
PyBoundArrayMethodObject *res;
// 为返回的 PyBoundArrayMethodObject 分配内存空间
res = PyObject_New(PyBoundArrayMethodObject, &PyBoundArrayMethod_Type);
if (res == NULL) {
return NULL;
}
res->method = NULL;
// 为 dtypes 数组分配内存空间,并复制 spec 中的 dtypes
res->dtypes = PyMem_Malloc(sizeof(PyArray_DTypeMeta *) * nargs);
if (res->dtypes == NULL) {
Py_DECREF(res);
PyErr_NoMemory();
return NULL;
}
for (int i = 0; i < nargs ; i++) {
Py_XINCREF(spec->dtypes[i]);
res->dtypes[i] = spec->dtypes[i];
}
// 为 res->method 分配内存空间,并初始化其字段
res->method = PyObject_New(PyArrayMethodObject, &PyArrayMethod_Type);
if (res->method == NULL) {
Py_DECREF(res);
PyErr_NoMemory();
return NULL;
}
memset((char *)(res->method) + sizeof(PyObject), 0,
sizeof(PyArrayMethodObject) - sizeof(PyObject));
// 将 spec 中的信息填充到 res->method 中
res->method->nin = spec->nin;
res->method->nout = spec->nout;
res->method->flags = spec->flags;
res->method->casting = spec->casting;
// 使用填充函数填充 res 中的 slots 信息
if (fill_arraymethod_from_slots(res, spec, private) < 0) {
Py_DECREF(res);
return NULL;
}
// 为方法名字符串分配内存空间,并复制 spec->name
Py_ssize_t length = strlen(spec->name);
res->method->name = PyMem_Malloc(length + 1);
if (res->method->name == NULL) {
Py_DECREF(res);
PyErr_NoMemory();
return NULL;
}
strcpy(res->method->name, spec->name);
return res;
}
static void
arraymethod_dealloc(PyObject *self)
{
// 释放 PyArrayMethodObject 对象中的 name 字段内存
PyArrayMethodObject *meth;
meth = ((PyArrayMethodObject *)self);
PyMem_Free(meth->name);
}
if (meth->wrapped_meth != NULL) {
Py_DECREF(meth->wrapped_meth);
for (int i = 0; i < meth->nin + meth->nout; i++) {
Py_XDECREF(meth->wrapped_dtypes[i]);
}
PyMem_Free(meth->wrapped_dtypes);
}
Py_TYPE(self)->tp_free(self);
/*
* 定义了一个名为 PyArrayMethod_Type 的 PyTypeObject 结构体,表示了一个 NumPy 中的数组方法类型。
* 这个类型结构体包含了类型的基本信息和方法,如名称、基本大小、析构函数、标志等。
*/
NPY_NO_EXPORT PyTypeObject PyArrayMethod_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "numpy._ArrayMethod", // 类型名称为 "numpy._ArrayMethod"
.tp_basicsize = sizeof(PyArrayMethodObject), // 基本大小为 PyArrayMethodObject 结构体的大小
.tp_dealloc = arraymethod_dealloc, // 析构函数为 arraymethod_dealloc 函数
.tp_flags = Py_TPFLAGS_DEFAULT, // 默认的类型标志
};
/*
* 定义了一个名为 boundarraymethod_repr 的静态函数,用于返回绑定数组方法对象的字符串表示形式。
* 函数根据绑定方法对象的 dtypes 属性构造一个表示形式,并返回一个新的 Python 字符串对象。
*/
static PyObject *
boundarraymethod_repr(PyBoundArrayMethodObject *self)
{
int nargs = self->method->nin + self->method->nout;
PyObject *dtypes = PyArray_TupleFromItems(
nargs, (PyObject **)self->dtypes, 0);
if (dtypes == NULL) {
return NULL; // 如果创建元组失败,返回 NULL
}
PyObject *repr = PyUnicode_FromFormat(
"<np._BoundArrayMethod `%s` for dtypes %S>",
self->method->name, dtypes);
Py_DECREF(dtypes); // 减少 dtypes 对象的引用计数
return repr; // 返回表示形式的字符串对象
}
/*
* 定义了一个名为 boundarraymethod_dealloc 的静态函数,用于释放绑定数组方法对象所占用的内存。
* 函数释放 dtypes 数组和 method 对象,并最终释放对象自身所占用的内存。
*/
static void
boundarraymethod_dealloc(PyObject *self)
{
PyBoundArrayMethodObject *meth;
meth = ((PyBoundArrayMethodObject *)self);
int nargs = meth->method->nin + meth->method->nout;
for (int i = 0; i < nargs; i++) {
Py_XDECREF(meth->dtypes[i]); // 释放每个 dtypes 元素对象的引用
}
PyMem_Free(meth->dtypes); // 释放 dtypes 数组的内存
Py_XDECREF(meth->method); // 释放 method 对象的引用
Py_TYPE(self)->tp_free(self); // 释放对象自身所占用的内存
}
/*
* 定义了一个名为 boundarraymethod__resolve_descriptors 的静态函数,用于解析描述符并返回相关信息。
* 函数验证传入的描述符元组是否符合预期格式,并根据验证结果返回相应的结果或错误。
*/
static PyObject *
boundarraymethod__resolve_descripors(
PyBoundArrayMethodObject *self, PyObject *descr_tuple)
{
int nin = self->method->nin;
int nout = self->method->nout;
PyArray_Descr *given_descrs[NPY_MAXARGS];
PyArray_Descr *loop_descrs[NPY_MAXARGS];
if (!PyTuple_CheckExact(descr_tuple) ||
PyTuple_Size(descr_tuple) != nin + nout) {
PyErr_Format(PyExc_TypeError,
"_resolve_descriptors() takes exactly one tuple with as many "
"elements as the method takes arguments (%d+%d).", nin, nout);
return NULL; // 如果描述符元组格式不符合预期,返回错误并 NULL
}
for (int i = 0; i < nin + nout; i++) {
// 获取描述符元组中第 i 个元素
PyObject *tmp = PyTuple_GetItem(descr_tuple, i);
// 如果获取失败,返回空指针异常
if (tmp == NULL) {
return NULL;
}
// 如果获取的元素是 Py_None
else if (tmp == Py_None) {
// 如果 i 小于输入个数 nin,设置类型错误并返回空指针异常
if (i < nin) {
PyErr_SetString(PyExc_TypeError,
"only output dtypes may be omitted (set to None).");
return NULL;
}
// 将给定描述符数组中第 i 个位置设置为 NULL
given_descrs[i] = NULL;
}
// 如果获取的元素是 PyArray_Descr 类型
else if (PyArray_DescrCheck(tmp)) {
// 如果获取的类型不是与 self->dtypes[i] 完全相同的类型,设置类型错误并返回空指针异常
if (Py_TYPE(tmp) != (PyTypeObject *)self->dtypes[i]) {
PyErr_Format(PyExc_TypeError,
"input dtype %S was not an exact instance of the bound "
"DType class %S.", tmp, self->dtypes[i]);
return NULL;
}
// 将给定描述符数组中第 i 个位置设置为 tmp 的 PyArray_Descr 类型
given_descrs[i] = (PyArray_Descr *)tmp;
}
// 如果获取的元素类型不是预期的 PyArray_Descr 类型或 Py_None,设置类型错误并返回空指针异常
else {
PyErr_SetString(PyExc_TypeError,
"dtype tuple can only contain dtype instances or None.");
return NULL;
}
}
// 初始化视图偏移量为 NPY_MIN_INTP
npy_intp view_offset = NPY_MIN_INTP;
// 解析描述符并获取转换规则
NPY_CASTING casting = self->method->resolve_descriptors(
self->method, self->dtypes, given_descrs, loop_descrs, &view_offset);
// 如果转换规则小于 0 并且发生异常,返回空指针异常
if (casting < 0 && PyErr_Occurred()) {
return NULL;
}
// 如果转换规则小于 0,返回具有整数、Py_None、Py_None 值的元组
else if (casting < 0) {
return Py_BuildValue("iOO", casting, Py_None, Py_None);
}
// 创建长度为 nin + nout 的元组对象 result_tuple
PyObject *result_tuple = PyTuple_New(nin + nout);
// 如果创建元组对象失败,返回空指针异常
if (result_tuple == NULL) {
return NULL;
}
// 将 loop_descrs 中的对象转移所有权给元组
for (int i = 0; i < nin + nout; i++) {
/* transfer ownership to the tuple. */
PyTuple_SET_ITEM(result_tuple, i, (PyObject *)loop_descrs[i]);
}
// 初始化视图偏移量对象为 Py_None
PyObject *view_offset_obj;
if (view_offset == NPY_MIN_INTP) {
Py_INCREF(Py_None);
view_offset_obj = Py_None;
}
// 否则,创建一个 PyLong 对象表示视图偏移量
else {
view_offset_obj = PyLong_FromSsize_t(view_offset);
// 如果创建失败,释放 result_tuple 并返回空指针异常
if (view_offset_obj == NULL) {
Py_DECREF(result_tuple);
return NULL;
}
}
/*
* The casting flags should be the most generic casting level.
* If no input is parametric, it must match exactly.
*
* (Note that these checks are only debugging checks.)
*/
// 初始化 parametric 变量为 0,用于检查是否存在参数化输入
int parametric = 0;
// 检查 self->dtypes 中是否存在参数化输入,设置 parametric 为 1 并退出循环
for (int i = 0; i < nin + nout; i++) {
if (NPY_DT_is_parametric(self->dtypes[i])) {
parametric = 1;
break;
}
}
// 如果当前对象的方法中的转换值不为 -1,则执行以下代码块
if (self->method->casting != -1) {
// 将当前函数中的 casting 值存入本地变量 cast
NPY_CASTING cast = casting;
// 如果当前对象的方法中的转换值与通过 PyArray_MinCastSafety 函数计算的值不匹配
if (self->method->casting !=
PyArray_MinCastSafety(cast, self->method->casting)) {
// 抛出运行时错误,说明描述符解析中的转换级别与存储的不匹配
PyErr_Format(PyExc_RuntimeError,
"resolve_descriptors cast level did not match stored one. "
"(set level is %d, got %d for method %s)",
self->method->casting, cast, self->method->name);
// 释放引用并返回 NULL
Py_DECREF(result_tuple);
Py_DECREF(view_offset_obj);
return NULL;
}
// 如果不是参数化情况下
if (!parametric) {
/*
* 非参数化情况只有在从等效转换到无转换时才可能不匹配
* (例如由于字节顺序的变化)。
*/
// 如果转换级别与当前对象的方法中的转换级别不同,并且当前方法的转换级别不是 NPY_EQUIV_CASTING
if (cast != self->method->casting &&
self->method->casting != NPY_EQUIV_CASTING) {
// 抛出运行时错误,说明转换级别发生变化,虽然转换是非参数化的,唯一可能的变化只能是从等效到无转换
PyErr_Format(PyExc_RuntimeError,
"resolve_descriptors cast level changed even though "
"the cast is non-parametric where the only possible "
"change should be from equivalent to no casting. "
"(set level is %d, got %d for method %s)",
self->method->casting, cast, self->method->name);
// 释放引用并返回 NULL
Py_DECREF(result_tuple);
Py_DECREF(view_offset_obj);
return NULL;
}
}
}
// 返回一个 Python 对象,格式化为 "iNN",包括 casting、result_tuple 和 view_offset_obj
return Py_BuildValue("iNN", casting, result_tuple, view_offset_obj);
/*
* TODO: This function is not public API, and certain code paths will need
* changes and especially testing if they were to be made public.
*/
static PyObject *
boundarraymethod__simple_strided_call(
PyBoundArrayMethodObject *self, PyObject *arr_tuple)
{
// 定义存储输入和输出数组、描述符、输出描述符、步长、参数长度等变量
PyArrayObject *arrays[NPY_MAXARGS];
PyArray_Descr *descrs[NPY_MAXARGS];
PyArray_Descr *out_descrs[NPY_MAXARGS];
char *args[NPY_MAXARGS];
npy_intp strides[NPY_MAXARGS];
Py_ssize_t length = -1;
int aligned = 1;
int nin = self->method->nin; // 获取输入参数数量
int nout = self->method->nout; // 获取输出参数数量
// 检查输入的参数是否为元组,并且元组长度是否等于输入参数数量加上输出参数数量
if (!PyTuple_CheckExact(arr_tuple) ||
PyTuple_Size(arr_tuple) != nin + nout) {
PyErr_Format(PyExc_TypeError,
"_simple_strided_call() takes exactly one tuple with as many "
"arrays as the method takes arguments (%d+%d).", nin, nout);
return NULL;
}
// 遍历输入和输出数组
for (int i = 0; i < nin + nout; i++) {
// 从元组中获取第i个元素作为输入或输出数组
PyObject *tmp = PyTuple_GetItem(arr_tuple, i);
if (tmp == NULL) {
return NULL; // 获取元素失败,返回错误
}
else if (!PyArray_CheckExact(tmp)) {
PyErr_SetString(PyExc_TypeError,
"All inputs must be NumPy arrays.");
return NULL; // 非NumPy数组,返回错误
}
// 将获取的数组强制转换为PyArrayObject类型
arrays[i] = (PyArrayObject *)tmp;
// 获取数组的描述符
descrs[i] = PyArray_DESCR(arrays[i]);
// 检查输入的描述符是否与绑定的DType类的类型匹配
if (Py_TYPE(descrs[i]) != (PyTypeObject *)self->dtypes[i]) {
PyErr_Format(PyExc_TypeError,
"input dtype %S was not an exact instance of the bound "
"DType class %S.", descrs[i], self->dtypes[i]);
return NULL; // 描述符类型不匹配,返回错误
}
// 检查数组是否为一维数组
if (PyArray_NDIM(arrays[i]) != 1) {
PyErr_SetString(PyExc_ValueError,
"All arrays must be one dimensional.");
return NULL; // 非一维数组,返回错误
}
// 获取第一个数组的长度作为参考长度
if (i == 0) {
length = PyArray_SIZE(arrays[i]);
}
else if (PyArray_SIZE(arrays[i]) != length) {
PyErr_SetString(PyExc_ValueError,
"All arrays must have the same length.");
return NULL; // 数组长度不一致,返回错误
}
// 如果是输出数组,检查是否可写
if (i >= nin) {
if (PyArray_FailUnlessWriteable(
arrays[i], "_simple_strided_call() output") < 0) {
return NULL; // 输出数组不可写,返回错误
}
}
// 获取数组的数据指针和步长
args[i] = PyArray_BYTES(arrays[i]);
strides[i] = PyArray_STRIDES(arrays[i])[0];
// 检查数组是否是对齐的
aligned &= PyArray_ISALIGNED(arrays[i]);
}
// 如果存在未对齐的数组,并且方法不支持非对齐输入,返回错误
if (!aligned && !(self->method->flags & NPY_METH_SUPPORTS_UNALIGNED)) {
PyErr_SetString(PyExc_ValueError,
"method does not support unaligned input.");
return NULL;
}
// 定义视图偏移和转换模式,解析描述符
npy_intp view_offset = NPY_MIN_INTP;
NPY_CASTING casting = self->method->resolve_descriptors(
self->method, self->dtypes, descrs, out_descrs, &view_offset);
// 如果 casting 小于 0,表示类型转换失败
if (casting < 0) {
// 保存当前的错误类型、值和回溯信息
PyObject *err_type = NULL, *err_value = NULL, *err_traceback = NULL;
PyErr_Fetch(&err_type, &err_value, &err_traceback);
// 设置类型错误异常并链式传播之前保存的异常信息
PyErr_SetString(PyExc_TypeError,
"cannot perform method call with the given dtypes.");
npy_PyErr_ChainExceptions(err_type, err_value, err_traceback);
// 返回 NULL 表示异常
return NULL;
}
// 标记是否有类型需要调整
int dtypes_were_adapted = 0;
// 遍历输入输出描述符列表,检查是否有需要调整的描述符
for (int i = 0; i < nin + nout; i++) {
/* NOTE: This check is probably much stricter than necessary... */
// 使用按位或运算符检查当前索引的描述符是否需要调整
dtypes_were_adapted |= descrs[i] != out_descrs[i];
// 减少输出描述符的引用计数,以便内存管理
Py_DECREF(out_descrs[i]);
}
// 如果有类型需要调整,则设置类型错误异常
if (dtypes_were_adapted) {
PyErr_SetString(PyExc_TypeError,
"_simple_strided_call(): requires dtypes to not require a cast "
"(must match exactly with `_resolve_descriptors()`).");
// 返回 NULL 表示异常
return NULL;
}
// 准备数组方法调用的上下文信息
PyArrayMethod_Context context = {
.caller = NULL,
.method = self->method,
.descriptors = descrs,
};
PyArrayMethod_StridedLoop *strided_loop = NULL;
NpyAuxData *loop_data = NULL;
NPY_ARRAYMETHOD_FLAGS flags = 0;
// 获取数组方法的跨步循环函数和相关数据
if (self->method->get_strided_loop(
&context, aligned, 0, strides,
&strided_loop, &loop_data, &flags) < 0) {
// 如果获取失败,返回 NULL 表示异常
return NULL;
}
/*
* TODO: 如果有请求,添加浮点数错误检查,并在标志允许的情况下释放 GIL。
*/
// 执行跨步循环函数,并获取返回值
int res = strided_loop(&context, args, &length, strides, loop_data);
// 如果存在循环数据,释放其占用的资源
if (loop_data != NULL) {
loop_data->free(loop_data);
}
// 如果执行结果小于 0,返回 NULL 表示异常
if (res < 0) {
return NULL;
}
// 返回 Py_None 表示成功执行,没有返回值需要传递
Py_RETURN_NONE;
/*
* Support for masked inner-strided loops. Masked inner-strided loops are
* only used in the ufunc machinery. So this special cases them.
* In the future it probably makes sense to create an::
*
* Arraymethod->get_masked_strided_loop()
*
* Function which this can wrap instead.
*/
/*
* 定义了一个结构体 _masked_stridedloop_data,用于支持带掩码的内部步进循环。
* 带掩码的内部步进循环仅在ufunc机制中使用。因此这里对它们进行特殊处理。
* 未来可能会创建一个函数 Arraymethod->get_masked_strided_loop() 来替代它。
*/
typedef struct {
NpyAuxData base;
PyArrayMethod_StridedLoop *unmasked_stridedloop;
NpyAuxData *unmasked_auxdata;
int nargs;
char *dataptrs[];
} _masked_stridedloop_data;
/*
* 释放 _masked_stridedloop_data 类型的辅助数据的函数
*/
static void
_masked_stridedloop_data_free(NpyAuxData *auxdata)
{
_masked_stridedloop_data *data = (_masked_stridedloop_data *)auxdata;
NPY_AUXDATA_FREE(data->unmasked_auxdata);
PyMem_Free(data);
}
/*
* 这个函数将一个常规的未掩码步进循环封装成带掩码的步进循环,仅对掩码为True的元素调用函数。
*
* TODO: Reducers(减少器)也使用此代码来实现带掩码的减少操作。
* 在合并它们之前,reducers 对广播有一个特例:当掩码步幅为0时,代码不会像 npy_memchr 当前那样检查所有元素。
* 如果广播掩码足够普遍,重新添加这样的优化可能是值得的。
*/
static int
generic_masked_strided_loop(PyArrayMethod_Context *context,
char *const *data, const npy_intp *dimensions,
const npy_intp *strides, NpyAuxData *_auxdata)
{
_masked_stridedloop_data *auxdata = (_masked_stridedloop_data *)_auxdata;
int nargs = auxdata->nargs;
PyArrayMethod_StridedLoop *strided_loop = auxdata->unmasked_stridedloop;
NpyAuxData *strided_loop_auxdata = auxdata->unmasked_auxdata;
char **dataptrs = auxdata->dataptrs;
memcpy(dataptrs, data, nargs * sizeof(char *));
char *mask = data[nargs];
npy_intp mask_stride = strides[nargs];
npy_intp N = dimensions[0];
/* Process the data as runs of unmasked values */
do {
Py_ssize_t subloopsize;
/* Skip masked values */
mask = npy_memchr(mask, 0, mask_stride, N, &subloopsize, 1);
for (int i = 0; i < nargs; i++) {
dataptrs[i] += subloopsize * strides[i];
}
N -= subloopsize;
/* Process unmasked values */
mask = npy_memchr(mask, 0, mask_stride, N, &subloopsize, 0);
if (subloopsize > 0) {
int res = strided_loop(context,
dataptrs, &subloopsize, strides, strided_loop_auxdata);
if (res != 0) {
return res;
}
for (int i = 0; i < nargs; i++) {
dataptrs[i] += subloopsize * strides[i];
}
N -= subloopsize;
}
} while (N > 0);
return 0;
}
/*
* Fetches a strided-loop function that supports a boolean mask as additional
* (last) operand to the strided-loop. It is otherwise largely identical to
* the `get_strided_loop` method which it wraps.
* This is the core implementation for the ufunc `where=...` keyword argument.
*
* NOTE: This function does not support `move_references` or inner dimensions.
*/
NPY_NO_EXPORT int
PyArrayMethod_GetMaskedStridedLoop(
PyArrayMethod_Context *context,
int aligned, npy_intp *fixed_strides,
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags)
{
_masked_stridedloop_data *data;
int nargs = context->method->nin + context->method->nout;
/* Allocate memory for _masked_stridedloop_data struct */
data = PyMem_Malloc(sizeof(_masked_stridedloop_data) +
sizeof(char *) * nargs);
if (data == NULL) {
PyErr_NoMemory(); /* Raise memory error if allocation fails */
return -1; /* Return -1 to indicate failure */
}
data->base.free = _masked_stridedloop_data_free; /* Set free function */
data->base.clone = NULL; /* Set clone function as not used */
data->unmasked_stridedloop = NULL; /* Initialize unmasked strided loop */
/* Retrieve unmasked strided loop using context method */
if (context->method->get_strided_loop(context,
aligned, 0, fixed_strides,
&data->unmasked_stridedloop, &data->unmasked_auxdata, flags) < 0) {
PyMem_Free(data); /* Free allocated memory on failure */
return -1; /* Return -1 to indicate failure */
}
*out_transferdata = (NpyAuxData *)data; /* Set output transfer data */
*out_loop = generic_masked_strided_loop; /* Set output loop function */
return 0; /* Return 0 to indicate success */
}
/* Definition of methods associated with PyBoundArrayMethodObject */
PyMethodDef boundarraymethod_methods[] = {
{"_resolve_descriptors", (PyCFunction)boundarraymethod__resolve_descripors,
METH_O, "Resolve the given dtypes."},
{"_simple_strided_call", (PyCFunction)boundarraymethod__simple_strided_call,
METH_O, "call on 1-d inputs and pre-allocated outputs (single call)."},
{NULL, 0, 0, NULL}, /* Sentinel indicating end of method definitions */
};
/* Function to retrieve whether the method supports unaligned inputs/outputs */
static PyObject *
boundarraymethod__supports_unaligned(PyBoundArrayMethodObject *self)
{
return PyBool_FromLong(self->method->flags & NPY_METH_SUPPORTS_UNALIGNED);
}
/* Getter definitions for PyBoundArrayMethodObject attributes */
PyGetSetDef boundarraymethods_getters[] = {
{"_supports_unaligned",
(getter)boundarraymethod__supports_unaligned, NULL,
"whether the method supports unaligned inputs/outputs.", NULL},
{NULL, NULL, NULL, NULL, NULL}, /* Sentinel indicating end of getter definitions */
};
/* Type definition for PyBoundArrayMethod_Type */
NPY_NO_EXPORT PyTypeObject PyBoundArrayMethod_Type = {
PyVarObject_HEAD_INIT(NULL, 0) /* Initialize base object */
.tp_name = "numpy._BoundArrayMethod", /* Type name */
.tp_basicsize = sizeof(PyBoundArrayMethodObject), /* Size of the object */
.tp_dealloc = boundarraymethod_dealloc, /* Deallocation function */
.tp_repr = (reprfunc)boundarraymethod_repr, /* Representation function */
.tp_flags = Py_TPFLAGS_DEFAULT, /* Default flags */
.tp_methods = boundarraymethod_methods, /* Methods associated */
.tp_getset = boundarraymethods_getters, /* Getters and setters */
};
.\numpy\numpy\_core\src\multiarray\array_method.h
// 定义防止使用过时 API 的宏
// 定义 _MULTIARRAYMODULE 宏
// 包含 Python 标准头文件
// 包含 NumPy 数组类型相关的头文件
extern "C" {
// 包含 NumPy 的 dtype API 头文件
/*
* 以下是 PyArrayMethod_MINIMAL_FLAGS 宏的定义:
* 默认最小标志位,目前仅指定了 NPY_METH_NO_FLOATINGPOINT_ERRORS
*/
/*
* 定义 PyArrayMethod_COMBINED_FLAGS 宏:
* 组合两个输入标志位,去除 PyArrayMethod_MINIMAL_FLAGS,然后合并剩余部分
*/
((NPY_ARRAYMETHOD_FLAGS)( \
((flags1 | flags2) & ~PyArrayMethod_MINIMAL_FLAGS) \
| (flags1 & flags2)))
/*
* 定义 PyArrayMethodObject_tag 结构体:
* 该结构体描述了数组方法的属性和函数指针,不建议公开
*/
typedef struct PyArrayMethodObject_tag {
PyObject_HEAD
char *name; // 方法名称
int nin, nout; // 输入和输出参数个数
NPY_CASTING casting; // 类型转换方式
NPY_ARRAYMETHOD_FLAGS flags; // 方法的标志位
void *static_data; // 方法可能需要的静态数据指针
// 以下是用于解析描述符和获取循环的函数指针
PyArrayMethod_ResolveDescriptorsWithScalar *resolve_descriptors_with_scalars;
PyArrayMethod_ResolveDescriptors *resolve_descriptors;
PyArrayMethod_GetLoop *get_strided_loop;
PyArrayMethod_GetReductionInitial *get_reduction_initial;
// 典型的循环函数指针
PyArrayMethod_StridedLoop *strided_loop;
PyArrayMethod_StridedLoop *contiguous_loop;
PyArrayMethod_StridedLoop *unaligned_strided_loop;
PyArrayMethod_StridedLoop *unaligned_contiguous_loop;
PyArrayMethod_StridedLoop *contiguous_indexed_loop;
// 用于包装在 umath 中定义的数组方法的结构体指针
struct PyArrayMethodObject_tag *wrapped_meth;
PyArray_DTypeMeta **wrapped_dtypes; // 包装的数据类型数组
// 用于翻译给定描述符和循环描述符的函数指针
PyArrayMethod_TranslateGivenDescriptors *translate_given_descrs;
PyArrayMethod_TranslateLoopDescriptors *translate_loop_descrs;
// 保留给遗留回退数组方法使用的区块
char legacy_initial[sizeof(npy_clongdouble)]; // 初始值存储
} PyArrayMethodObject;
/*
* We will sometimes have to create a ArrayMethod and allow passing it around,
* similar to `instance.method` returning a bound method, e.g. a function like
* `ufunc.resolve()` can return a bound object.
* The current main purpose of the BoundArrayMethod is that it holds on to the
* `dtypes` (the classes), so that the `ArrayMethod` (e.g. for casts) will
* not create references cycles. In principle, it could hold any information
* which is also stored on the ufunc (and thus does not need to be repeated
* on the `ArrayMethod` itself.
*/
typedef struct {
PyObject_HEAD
// 指向 PyArray_DTypeMeta 指针的数组,用于存储数据类型信息
PyArray_DTypeMeta **dtypes;
// 指向 PyArrayMethodObject 对象的指针,表示绑定的数组方法对象
PyArrayMethodObject *method;
} PyBoundArrayMethodObject;
// 外部声明 PyArrayMethod_Type 类型
extern NPY_NO_EXPORT PyTypeObject PyArrayMethod_Type;
// 外部声明 PyBoundArrayMethod_Type 类型
extern NPY_NO_EXPORT PyTypeObject PyBoundArrayMethod_Type;
/*
* Used internally (initially) for real to complex loops only
*/
// NPY_NO_EXPORT 指示该函数在本文件中使用,作用是获取默认的分步循环函数
NPY_NO_EXPORT int
npy_default_get_strided_loop(
PyArrayMethod_Context *context,
int aligned, int NPY_UNUSED(move_references), const npy_intp *strides,
PyArrayMethod_StridedLoop **out_loop, NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags);
// NPY_NO_EXPORT 指示该函数在本文件中使用,作用是获取带掩码的分步循环函数
NPY_NO_EXPORT int
PyArrayMethod_GetMaskedStridedLoop(
PyArrayMethod_Context *context,
int aligned,
npy_intp *fixed_strides,
PyArrayMethod_StridedLoop **out_loop,
NpyAuxData **out_transferdata,
NPY_ARRAYMETHOD_FLAGS *flags);
// NPY_NO_EXPORT 指示该函数在本文件中使用,作用是根据规范创建数组方法对象
NPY_NO_EXPORT PyObject *
PyArrayMethod_FromSpec(PyArrayMethod_Spec *spec);
/*
* TODO: This function is the internal version, and its error paths may
* need better tests when a public version is exposed.
*/
// NPY_NO_EXPORT 指示该函数在本文件中使用,作用是根据规范创建内部版本的绑定数组方法对象
NPY_NO_EXPORT PyBoundArrayMethodObject *
PyArrayMethod_FromSpec_int(PyArrayMethod_Spec *spec, int priv);
}
.\numpy\numpy\_core\src\multiarray\buffer.c
/*
* 定义 NPY_NO_DEPRECATED_API,避免使用已废弃的 NumPy API 版本
* 定义 _MULTIARRAYMODULE,暂未说明具体用途
*/
/*
* 清除 PY_SSIZE_T_CLEAN 宏定义,确保 Py_ssize_t 被正确声明
*/
/*
* 包含 Python.h 头文件,提供 Python C API 支持
* 包含 structmember.h 头文件,用于定义结构体成员
*/
/*
* 包含 NumPy 相关头文件
*/
/*
* 包含 NumPy 的配置文件 npy_config.h
*/
/*
* 包含 NumPy 的 Python 兼容性支持文件 npy_pycompat.h
*/
/*
* 包含 NumPy 的缓冲区协议实现 npy_buffer.h
* 包含 NumPy 的通用函数和宏定义 common.h
* 包含 NumPy 的操作系统相关功能 numpyos.h
* 包含 NumPy 的数组对象定义 arrayobject.h
* 包含 NumPy 的标量类型定义 scalartypes.h
* 包含 NumPy 的数据类型元数据定义 dtypemeta.h
*/
/*************************************************************************
**************** 实现缓冲区协议 ****************************
*************************************************************************/
/*************************************************************************
* PEP 3118 缓冲区协议
*
* 实现 PEP 3118 有些复杂,因为需要考虑以下要求:
*
* - 不向 ndarray 或 descr 结构体添加新成员,以保持二进制兼容性。
* (同时,向其中添加项目实际上并不是很有用,因为可变性问题阻碍了数组与缓冲区视图之间的一对一关系。)
*
* - 不使用 bf_releasebuffer,因为这会阻止 PyArg_ParseTuple("s#", ... 的工作。
* 违反这一点会导致在 Python 2.6 上引起多个向后兼容性问题。
*
* - 在数组原地重塑或其 dtype 被修改时,要正确处理。
*
* 下面采取的解决方案是手动跟踪为 Py_buffers 分配的内存。
*************************************************************************/
/*
* 格式字符串转换器
*
* 将 PyArray_Descr 转换为 PEP 3118 格式字符串。
*/
/* 快速字符串 'class' */
typedef struct {
char *s;
size_t allocated;
size_t pos;
} _tmp_string_t;
/*
* 将字符 c 追加到字符串 s 中
*/
static int
_append_char(_tmp_string_t *s, char c)
{
if (s->pos >= s->allocated) {
char *p;
size_t to_alloc = (s->allocated == 0) ? INIT_SIZE : (2 * s->allocated);
p = PyObject_Realloc(s->s, to_alloc);
if (p == NULL) {
PyErr_SetString(PyExc_MemoryError, "memory allocation failed");
return -1;
}
s->s = p;
s->allocated = to_alloc;
}
s->s[s->pos] = c;
++s->pos;
return 0;
}
/*
* 将字符串 p 追加到字符串 s 中
*/
static int
_append_str(_tmp_string_t *s, char const *p)
{
for (; *p != '\0'; p++) {
if (_append_char(s, *p) < 0) {
return -1;
}
}
return 0;
}
/*
* 向字符串 str 追加一个 PEP3118 格式的字段名 ":name:"
*/
static int
_append_field_name(_tmp_string_t *str, PyObject *name)
{
int ret = -1;
char *p;
Py_ssize_t len;
PyObject *tmp;
/* FIXME: XXX -- should it use UTF-8 here? */
tmp = PyUnicode_AsUTF8String(name);
if (tmp == NULL || PyBytes_AsStringAndSize(tmp, &p, &len) < 0) {
PyErr_Clear();
PyErr_SetString(PyExc_ValueError, "invalid field name");
goto fail;
}
if (_append_char(str, ':') < 0) {
goto fail;
}
while (len > 0) {
// 当还有字符需要处理时进入循环
if (*p == ':') {
// 如果当前字符为':',则抛出值错误异常并提示不允许在缓冲字段名中使用':'
PyErr_SetString(PyExc_ValueError,
"':' is not an allowed character in buffer "
"field names");
// 跳转到失败标签,表示处理失败
goto fail;
}
// 将当前字符追加到字符串对象中,如果追加失败则跳转到失败标签
if (_append_char(str, *p) < 0) {
goto fail;
}
// 移动到下一个字符
++p;
// 减少待处理字符数
--len;
}
// 在字符串对象末尾追加字符':',如果失败则跳转到失败标签
if (_append_char(str, ':') < 0) {
goto fail;
}
// 设置返回值为0,表示成功处理完所有字符
ret = 0;
fail:
// 释放临时对象的 Python 引用计数
Py_XDECREF(tmp);
// 返回操作的结果
return ret;
}
/*
* 如果一个类型在给定数组的每个项中都是对齐的,
* 并且描述符元素大小是对齐的倍数,
* 并且数组数据按照对齐的粒度定位,则返回非零值。
*/
static inline int
_is_natively_aligned_at(PyArray_Descr *descr,
PyArrayObject *arr, Py_ssize_t offset)
{
int k;
if (NPY_LIKELY(descr == PyArray_DESCR(arr))) {
/*
* 如果描述符与数组的描述符相同,可以假设数组的对齐是正确的。
*/
assert(offset == 0);
if (PyArray_ISALIGNED(arr)) {
assert(descr->elsize % descr->alignment == 0);
return 1;
}
return 0;
}
if ((Py_ssize_t)(PyArray_DATA(arr)) % descr->alignment != 0) {
return 0;
}
if (offset % descr->alignment != 0) {
return 0;
}
if (descr->elsize % descr->alignment) {
return 0;
}
for (k = 0; k < PyArray_NDIM(arr); ++k) {
if (PyArray_DIM(arr, k) > 1) {
if (PyArray_STRIDE(arr, k) % descr->alignment != 0) {
return 0;
}
}
}
return 1;
}
/*
* 根据描述符填充字符串 str,生成适合 PEP 3118 格式的字符串。
* 对于结构化数据类型,递归调用自身。每次调用在偏移量上扩展 str,
* 更新偏移量,并使用 descr->byteorder(以及可能的 obj 中的字节顺序)
* 来确定字节顺序字符。
*
* 返回 0 表示成功,-1 表示失败
*/
static int
_buffer_format_string(PyArray_Descr *descr, _tmp_string_t *str,
PyObject* obj, Py_ssize_t *offset,
char *active_byteorder)
{
int k;
char _active_byteorder = '@'; // 默认的字节顺序字符
Py_ssize_t _offset = 0; // 默认的偏移量
if (active_byteorder == NULL) {
active_byteorder = &_active_byteorder;
}
if (offset == NULL) {
offset = &_offset;
}
if (PyDataType_HASSUBARRAY(descr)) {
_PyArray_LegacyDescr *ldescr = (_PyArray_LegacyDescr *)descr;
PyObject *item, *subarray_tuple;
Py_ssize_t total_count = 1;
Py_ssize_t dim_size;
Py_ssize_t old_offset;
char buf[128];
int ret;
if (PyTuple_Check(ldescr->subarray->shape)) {
subarray_tuple = ldescr->subarray->shape;
Py_INCREF(subarray_tuple);
}
else {
subarray_tuple = Py_BuildValue("(O)", ldescr->subarray->shape);
}
if (_append_char(str, '(') < 0) {
ret = -1;
goto subarray_fail;
}
for (k = 0; k < PyTuple_GET_SIZE(subarray_tuple); ++k) {
if (k > 0) {
if (_append_char(str, ',') < 0) {
ret = -1;
goto subarray_fail;
}
}
item = PyTuple_GET_ITEM(subarray_tuple, k);
dim_size = PyNumber_AsSsize_t(item, NULL);
PyOS_snprintf(buf, sizeof(buf), "%ld", (long)dim_size);
if (_append_str(str, buf) < 0) {
ret = -1;
goto subarray_fail;
}
total_count *= dim_size;
}
if (_append_char(str, ')') < 0) {
ret = -1;
goto subarray_fail;
}
old_offset = *offset;
ret = _buffer_format_string(ldescr->subarray->base, str, obj, offset,
active_byteorder);
*offset = old_offset + (*offset - old_offset) * total_count;
subarray_fail:
Py_DECREF(subarray_tuple);
return ret;
}
else if (PyDataType_HASFIELDS(descr)) {
// 如果描述符包含字段信息
_PyArray_LegacyDescr *ldescr = (_PyArray_LegacyDescr *)descr;
// 将偏移量保存到基础偏移量中
Py_ssize_t base_offset = *offset;
// 向字符串中追加 'T{',表示开始一个复合类型描述
if (_append_str(str, "T{") < 0) return -1;
// 遍历复合类型中的字段
for (k = 0; k < PyTuple_GET_SIZE(ldescr->names); ++k) {
PyObject *name, *item, *offset_obj;
PyArray_Descr *child;
Py_ssize_t new_offset;
int ret;
// 获取字段名
name = PyTuple_GET_ITEM(ldescr->names, k);
// 从字段字典中获取字段信息
item = PyDict_GetItem(ldescr->fields, name);
// 获取字段的数据类型描述符和偏移量对象
child = (PyArray_Descr*)PyTuple_GetItem(item, 0);
offset_obj = PyTuple_GetItem(item, 1);
// 将偏移量对象转换为 Py_ssize_t 类型
new_offset = PyLong_AsLong(offset_obj);
// 检查是否转换出错
if (error_converting(new_offset)) {
return -1;
}
// 根据基础偏移量计算新的偏移量
new_offset += base_offset;
/* 手动插入填充 */
// 如果当前偏移量大于新的偏移量,说明需要插入填充字节
if (*offset > new_offset) {
PyErr_SetString(
PyExc_ValueError,
"dtypes with overlapping or out-of-order fields are not "
"representable as buffers. Consider reordering the fields."
);
return -1;
}
// 插入填充字节,直到当前偏移量达到新的偏移量
while (*offset < new_offset) {
if (_append_char(str, 'x') < 0) return -1;
++*offset;
}
/* 插入子项 */
// 递归处理子项的格式字符串
ret = _buffer_format_string(child, str, obj, offset,
active_byteorder);
// 检查处理子项是否出错
if (ret < 0) {
return -1;
}
/* 插入字段名 */
// 向字符串中插入字段名
if (_append_field_name(str, name) < 0) return -1;
}
// 向字符串中追加 '}',表示复合类型描述结束
if (_append_char(str, '}') < 0) return -1;
}
// 返回成功标志
return 0;
/* 结构体定义:用于存储为提供缓冲区接口而需要的每个数组的额外数据 */
typedef struct _buffer_info_t_tag {
char *format; // 缓冲区数据的格式
int ndim; // 数组的维度
Py_ssize_t *strides; // 数组的步长信息
Py_ssize_t *shape; // 数组的形状信息
struct _buffer_info_t_tag *next; // 指向下一个缓冲区信息结构体的指针
} _buffer_info_t;
/* 创建并返回一个新的缓冲区信息结构体 */
static _buffer_info_t*
_buffer_info_new(PyObject *obj, int flags)
{
/* 缓冲区信息被缓存为 PyLongObjects,这使得它们在 valgrind 中看起来像是无法访问的丢失内存 */
_buffer_info_t *info; // 缓冲区信息结构体指针
_tmp_string_t fmt = {NULL, 0, 0}; // 临时字符串结构体
int k; // 循环变量
PyArray_Descr *descr = NULL; // 数组描述符指针
int err = 0; // 错误码
/* 如果 obj 是一个标量对象且类型是 Void */
if (PyArray_IsScalar(obj, Void)) {
info = PyObject_Malloc(sizeof(_buffer_info_t)); // 分配缓冲区信息结构体的内存空间
if (info == NULL) {
PyErr_NoMemory(); // 内存分配失败,抛出内存错误异常
goto fail; // 跳转到失败处理标签
}
info->ndim = 0; // 设置数组维度为 0
info->shape = NULL; // 形状信息为空
info->strides = NULL; // 步长信息为空
/* 从标量对象创建数组描述符 */
descr = PyArray_DescrFromScalar(obj);
if (descr == NULL) {
goto fail; // 如果创建描述符失败,跳转到失败处理标签
}
}
else {
// 断言对象是否为 NumPy 数组
assert(PyArray_Check(obj));
// 将对象转换为 PyArrayObject 类型
PyArrayObject * arr = (PyArrayObject *)obj;
// 分配内存给 info 结构体,包括 shape 和 strides
info = PyObject_Malloc(sizeof(_buffer_info_t) +
sizeof(Py_ssize_t) * PyArray_NDIM(arr) * 2);
// 如果内存分配失败,设置内存错误并跳转到失败标签
if (info == NULL) {
PyErr_NoMemory();
goto fail;
}
/* 填充 shape 和 strides */
info->ndim = PyArray_NDIM(arr);
// 如果数组维度为 0,shape 和 strides 都为 NULL
if (info->ndim == 0) {
info->shape = NULL;
info->strides = NULL;
}
else {
// 分配内存给 shape 和 strides,确保地址对齐
info->shape = (npy_intp *)((char *)info + sizeof(_buffer_info_t));
assert((size_t)info->shape % sizeof(npy_intp) == 0);
info->strides = info->shape + PyArray_NDIM(arr);
/*
* 一些缓冲区使用者可能希望连续的缓冲区在维度为 1 时也有格式良好的 strides,
* 但是我们在内部不保证这一点。因此,对于连续数组,需要重新计算 strides。
*/
int f_contiguous = (flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS;
if (PyArray_IS_C_CONTIGUOUS(arr) && !(
f_contiguous && PyArray_IS_F_CONTIGUOUS(arr))) {
Py_ssize_t sd = PyArray_ITEMSIZE(arr);
for (k = info->ndim-1; k >= 0; --k) {
info->shape[k] = PyArray_DIMS(arr)[k];
info->strides[k] = sd;
sd *= info->shape[k];
}
}
else if (PyArray_IS_F_CONTIGUOUS(arr)) {
Py_ssize_t sd = PyArray_ITEMSIZE(arr);
for (k = 0; k < info->ndim; ++k) {
info->shape[k] = PyArray_DIMS(arr)[k];
info->strides[k] = sd;
sd *= info->shape[k];
}
}
else {
// 非连续数组,直接复制 PyArray 的维度和 strides
for (k = 0; k < PyArray_NDIM(arr); ++k) {
info->shape[k] = PyArray_DIMS(arr)[k];
info->strides[k] = PyArray_STRIDES(arr)[k];
}
}
}
descr = PyArray_DESCR(arr);
// 增加对象的引用计数
Py_INCREF(descr);
}
/* 填充格式 */
// 如果 flags 包含 PyBUF_FORMAT 标志位
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
// 获取数组的格式字符串
err = _buffer_format_string(descr, &fmt, obj, NULL, NULL);
// 减少描述符的引用计数
Py_DECREF(descr);
// 如果获取格式字符串出错,跳转到失败标签
if (err != 0) {
goto fail;
}
// 如果追加字符失败,跳转到失败标签
if (_append_char(&fmt, '\0') < 0) {
goto fail;
}
// 将格式字符串赋值给 info 的 format 字段
info->format = fmt.s;
}
else {
// 减少描述符的引用计数
Py_DECREF(descr);
// 将 format 字段设置为 NULL
info->format = NULL;
}
info->next = NULL;
// 返回 info 结构体
return info;
fail:
PyObject_Free(fmt.s);
PyObject_Free(info);
return NULL;
}
/* Compare two info structures */
static Py_ssize_t
_buffer_info_cmp(_buffer_info_t *a, _buffer_info_t *b)
{
Py_ssize_t c;
int k;
// 比较两个 _buffer_info_t 结构体的 format 字段
if (a->format != NULL && b->format != NULL) {
c = strcmp(a->format, b->format);
if (c != 0) return c;
}
// 比较两个 _buffer_info_t 结构体的 ndim 字段
c = a->ndim - b->ndim;
if (c != 0) return c;
// 逐个比较两个 _buffer_info_t 结构体的 shape 和 strides 数组
for (k = 0; k < a->ndim; ++k) {
c = a->shape[k] - b->shape[k];
if (c != 0) return c;
c = a->strides[k] - b->strides[k];
if (c != 0) return c;
}
// 结构体比较完成,返回相等
return 0;
}
/*
* Tag the buffer info pointer by adding 2 (unless it is NULL to simplify
* object initialization).
* The linked list of buffer-infos was appended to the array struct in
* NumPy 1.20. Tagging the pointer gives us a chance to raise/print
* a useful error message instead of crashing hard if a C-subclass uses
* the same field.
*/
static inline void *
buffer_info_tag(void *buffer_info)
{
// 如果 buffer_info 为 NULL,则直接返回 NULL
if (buffer_info == NULL) {
return buffer_info;
}
else {
// 否则,在 buffer_info 地址上加 3 并返回,用于标记
return (void *)((uintptr_t)buffer_info + 3);
}
}
static inline int
_buffer_info_untag(
void *tagged_buffer_info, _buffer_info_t **buffer_info, PyObject *obj)
{
// 如果 tagged_buffer_info 为 NULL,则直接返回,无需解标记
if (tagged_buffer_info == NULL) {
*buffer_info = NULL;
return 0;
}
// 检查 tagged_buffer_info 是否正确标记
if (NPY_UNLIKELY(((uintptr_t)tagged_buffer_info & 0x7) != 3)) {
PyErr_Format(PyExc_RuntimeError,
"Object of type %S appears to be C subclassed NumPy array, "
"void scalar, or allocated in a non-standard way."
"NumPy reserves the right to change the size of these "
"structures. Projects are required to take this into account "
"by either recompiling against a specific NumPy version or "
"padding the struct and enforcing a maximum NumPy version.",
Py_TYPE(obj));
return -1;
}
// 解标记 tagged_buffer_info 并返回正确的 _buffer_info_t 结构体指针
*buffer_info = (void *)((uintptr_t)tagged_buffer_info - 3);
return 0;
}
/*
* NOTE: for backward compatibility (esp. with PyArg_ParseTuple("s#", ...))
* we do *not* define bf_releasebuffer at all.
*
* Instead, any extra data allocated with the buffer is released only in
* array_dealloc.
*
* Ensuring that the buffer stays in place is taken care by refcounting;
* ndarrays do not reallocate if there are references to them, and a buffer
* view holds one reference.
*
* This is stored in the array's _buffer_info slot (currently as a void *).
*/
static void
_buffer_info_free_untagged(void *_buffer_info)
{
_buffer_info_t *next = _buffer_info;
// 释放整个链表上的 _buffer_info_t 结构体及其包含的内存
while (next != NULL) {
_buffer_info_t *curr = next;
next = curr->next;
if (curr->format) {
PyObject_Free(curr->format);
}
/* Shape is allocated as part of info */
PyObject_Free(curr);
}
}
/*
* 检查指针是否已标记,并释放缓存列表。
* (标记检查仅适用于由于结构大小在1.20中更改而进行的过渡)
*/
NPY_NO_EXPORT int
_buffer_info_free(void *buffer_info, PyObject *obj)
{
_buffer_info_t *untagged_buffer_info;
// 如果解除标记失败,则返回-1
if (_buffer_info_untag(buffer_info, &untagged_buffer_info, obj) < 0) {
return -1;
}
// 释放未标记的缓存信息
_buffer_info_free_untagged(untagged_buffer_info);
return 0;
}
/*
* 获取缓冲区信息,返回传入的旧信息或添加保持(因此替换)旧信息的新缓冲区信息。
*/
static _buffer_info_t*
_buffer_get_info(void **buffer_info_cache_ptr, PyObject *obj, int flags)
{
_buffer_info_t *info = NULL;
_buffer_info_t *stored_info; /* 当前存储的第一个缓冲区信息 */
// 如果解除标记失败,则返回空指针
if (_buffer_info_untag(*buffer_info_cache_ptr, &stored_info, obj) < 0) {
return NULL;
}
_buffer_info_t *old_info = stored_info;
/* 计算信息(在简单情况下可以跳过这一步骤将会更好) */
// 创建新的缓冲区信息对象
info = _buffer_info_new(obj, flags);
if (info == NULL) {
return NULL;
}
// 如果存在旧信息并且新旧信息不同,则进行比较
if (old_info != NULL && _buffer_info_cmp(info, old_info) != 0) {
_buffer_info_t *next_info = old_info->next;
old_info = NULL; /* 不能使用此旧信息,但可能使用下一个 */
// 如果 info 的维度大于1且下一个信息不为空,则比较两者
if (info->ndim > 1 && next_info != NULL) {
/*
* 有些数组是C和F连续的,如果它们有多于一个维度,
* 缓冲区信息可能在两者之间不同,因为长度为1的维度的步幅可能会调整。
* 如果我们导出这两个缓冲区,第一个存储的可能是另一个连续性的缓冲区,
* 因此检查两者。
* 这在所有其他情况下通常是非常不可能的,因为在所有其他情况下,
* 第一个将与第一个匹配,除非数组元数据被就地修改(这是不鼓励的)。
*/
if (_buffer_info_cmp(info, next_info) == 0) {
old_info = next_info;
}
}
}
// 如果存在旧信息,则处理新旧信息的格式相等性
if (old_info != NULL) {
/*
* 如果其中一个 info->format 未设置格式(意味着格式是任意的并且可以修改)。
* 如果新信息有格式,但我们重用旧信息,则将所有权转移到旧信息。
*/
if (old_info->format == NULL) {
old_info->format = info->format;
info->format = NULL;
}
// 释放新信息的未标记对象并返回旧信息
_buffer_info_free_untagged(info);
info = old_info;
}
else {
/* 将新信息插入到链接的缓冲区信息列表的第一项中 */
info->next = stored_info;
*buffer_info_cache_ptr = buffer_info_tag(info);
}
return info;
}
/*
* 为ndarray检索缓冲区
*/
static int
array_getbuffer(PyObject *obj, Py_buffer *view, int flags)
{
PyArrayObject *self;
_buffer_info_t *info = NULL;
// 定义指向 _buffer_info_t 结构体的指针变量,并初始化为 NULL
self = (PyArrayObject*)obj;
// 将 obj 强制转换为 PyArrayObject 类型,并赋值给 self
/* Check whether we can provide the wanted properties */
// 检查是否可以提供所需的属性
if ((flags & PyBUF_C_CONTIGUOUS) == PyBUF_C_CONTIGUOUS &&
!PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)) {
// 如果请求的是 C 连续的缓冲区,并且数组不是 C 连续的
PyErr_SetString(PyExc_ValueError, "ndarray is not C-contiguous");
// 设置异常,指示数组不是 C 连续的
goto fail;
// 跳转到失败处理部分
}
if ((flags & PyBUF_F_CONTIGUOUS) == PyBUF_F_CONTIGUOUS &&
!PyArray_CHKFLAGS(self, NPY_ARRAY_F_CONTIGUOUS)) {
// 如果请求的是 Fortran 连续的缓冲区,并且数组不是 Fortran 连续的
PyErr_SetString(PyExc_ValueError, "ndarray is not Fortran contiguous");
// 设置异常,指示数组不是 Fortran 连续的
goto fail;
// 跳转到失败处理部分
}
if ((flags & PyBUF_ANY_CONTIGUOUS) == PyBUF_ANY_CONTIGUOUS
&& !PyArray_ISONESEGMENT(self)) {
// 如果请求的是任意连续的缓冲区,并且数组不是单一段的
PyErr_SetString(PyExc_ValueError, "ndarray is not contiguous");
// 设置异常,指示数组不是连续的
goto fail;
// 跳转到失败处理部分
}
if ((flags & PyBUF_STRIDES) != PyBUF_STRIDES &&
!PyArray_CHKFLAGS(self, NPY_ARRAY_C_CONTIGUOUS)) {
// 如果请求的是非步幅数组,但数组不是 C 连续的
PyErr_SetString(PyExc_ValueError, "ndarray is not C-contiguous");
// 设置异常,指示数组不是 C 连续的
goto fail;
// 跳转到失败处理部分
}
if ((flags & PyBUF_WRITEABLE) == PyBUF_WRITEABLE) {
// 如果请求的缓冲区可写
if (PyArray_FailUnlessWriteable(self, "buffer source array") < 0) {
// 检查数组是否可写,如果不可写则设置异常
goto fail;
// 跳转到失败处理部分
}
}
if (view == NULL) {
// 如果传入的视图指针为空
PyErr_SetString(PyExc_ValueError, "NULL view in getbuffer");
// 设置异常,表示在获取缓冲区时传入了空视图
goto fail;
// 跳转到失败处理部分
}
/* Fill in information (and add it to _buffer_info if necessary) */
// 填充信息(如果需要,将其添加到 _buffer_info 中)
info = _buffer_get_info(
&((PyArrayObject_fields *)self)->_buffer_info, obj, flags);
// 调用 _buffer_get_info 函数获取缓冲区信息,并赋值给 info
if (info == NULL) {
// 如果获取信息失败
goto fail;
// 跳转到失败处理部分
}
view->buf = PyArray_DATA(self);
// 设置视图的缓冲区指针为数组的数据指针
view->suboffsets = NULL;
// 设置子偏移为 NULL
view->itemsize = PyArray_ITEMSIZE(self);
// 设置视图的每个项的大小为数组的每个项的大小
/*
* If a read-only buffer is requested on a read-write array, we return a
* read-write buffer as per buffer protocol.
* We set a requested buffer to readonly also if the array will be readonly
* after a deprecation. This jumps the deprecation, but avoiding the
* warning is not convenient here. A warning is given if a writeable
* buffer is requested since `PyArray_FailUnlessWriteable` is called above
* (and clears the `NPY_ARRAY_WARN_ON_WRITE` flag).
*/
// 如果请求在可读写数组上获取只读缓冲区,则根据缓冲区协议返回可读写的缓冲区。
// 如果数组在弃用后将变为只读,我们也将请求的缓冲区设置为只读。
// 这跳过了弃用,但这里避免警告并不方便。如果请求可写的缓冲区,则会发出警告,因为上面调用了 `PyArray_FailUnlessWriteable`(并清除了 `NPY_ARRAY_WARN_ON_WRITE` 标志)。
view->readonly = (!PyArray_ISWRITEABLE(self) ||
PyArray_CHKFLAGS(self, NPY_ARRAY_WARN_ON_WRITE));
// 设置视图的只读属性,如果数组不可写或设置了 `NPY_ARRAY_WARN_ON_WRITE` 标志,则设置为只读
view->internal = NULL;
// 设置视图的内部指针为 NULL
view->len = PyArray_NBYTES(self);
// 设置视图的长度为数组的字节大小
if ((flags & PyBUF_FORMAT) == PyBUF_FORMAT) {
view->format = info->format;
// 如果请求获取格式化信息,则将视图的格式指针设置为 info 中的格式信息
} else {
view->format = NULL;
// 否则将格式指针设置为 NULL
}
if ((flags & PyBUF_ND) == PyBUF_ND) {
view->ndim = info->ndim;
// 如果请求获取维度信息,则将视图的维度设置为 info 中的维度信息
view->shape = info->shape;
// 并将形状指针设置为 info 中的形状信息
}
else {
view->ndim = 0;
// 否则将维度设置为 0
view->shape = NULL;
// 并将形状指针设置为 NULL
}
if ((flags & PyBUF_STRIDES) == PyBUF_STRIDES) {
view->strides = info->strides;
// 如果请求获取步幅信息,则将视图的步幅指针设置为 info 中的步幅信息
}
else {
view->strides = NULL;
// 否则将步幅指针设置为 NULL
}
view->obj = (PyObject*)self;
// 将视图的对象指针设置为数组对象的指针
Py_INCREF(self);
// 增加数组对象的引用计数
return 0;
// 返回 0,表示获取缓冲区成功
fail:
// 失败处理部分的标签
/*
* 返回错误代码 -1,表示操作失败
*/
fail:
return -1;
}
/*
* 从 void scalar 中获取缓冲区(可以包含任意复杂类型),
* 定义在 buffer.c 中,因为它需要复杂格式构建逻辑。
*/
NPY_NO_EXPORT int
void_getbuffer(PyObject *self, Py_buffer *view, int flags)
{
// 将 self 强制转换为 PyVoidScalarObject 类型
PyVoidScalarObject *scalar = (PyVoidScalarObject *)self;
// 如果传入的 flags 包含 PyBUF_WRITABLE 标志,设置错误信息并返回 -1
if (flags & PyBUF_WRITABLE) {
PyErr_SetString(PyExc_BufferError, "scalar buffer is readonly");
return -1;
}
// 设置视图的维度为 0,形状为空,步长为空,子偏移为空
view->ndim = 0;
view->shape = NULL;
view->strides = NULL;
view->suboffsets = NULL;
// 设置视图的长度和项大小为 scalar 对象描述符的元素大小
view->len = scalar->descr->elsize;
view->itemsize = scalar->descr->elsize;
// 视图为只读
view->readonly = 1;
view->suboffsets = NULL;
// 增加 self 的引用计数
Py_INCREF(self);
// 视图的对象指针指向 self
view->obj = self;
// 视图的缓冲区指针指向 scalar 的值
view->buf = scalar->obval;
// 如果 flags 不包含 PyBUF_FORMAT 标志,设置视图的格式为 NULL 并返回 0
if (((flags & PyBUF_FORMAT) != PyBUF_FORMAT)) {
/* It is unnecessary to find the correct format */
view->format = NULL;
return 0;
}
/*
* 如果正在导出格式,我们需要使用 _buffer_get_info 函数
* 来找到正确的格式。此格式也必须存储,因为理论上它可以改变
* (实际上它不应该改变)。
*/
// 使用 _buffer_get_info 函数获取格式信息,存储在 info 中
_buffer_info_t *info = _buffer_get_info(&scalar->_buffer_info, self, flags);
// 如果 info 为 NULL,释放 self 的引用计数并返回 -1
if (info == NULL) {
Py_DECREF(self);
return -1;
}
// 设置视图的格式为 info 中的格式
view->format = info->format;
// 返回成功标志 0
return 0;
}
/*************************************************************************/
// array_as_buffer 结构体,包含获取缓冲区的函数指针和释放缓冲区的函数指针
NPY_NO_EXPORT PyBufferProcs array_as_buffer = {
(getbufferproc)array_getbuffer,
(releasebufferproc)0,
};
/*************************************************************************
* 将 PEP 3118 格式字符串转换为 PyArray_Descr 结构体
*/
// 快速版本的 _descriptor_from_pep3118_format 函数声明
static int
_descriptor_from_pep3118_format_fast(char const *s, PyObject **result);
// 根据字母、本地化标志和复杂性标志返回数据类型
static int
_pep3118_letter_to_type(char letter, int native, int is_complex);
// 将 PEP 3118 格式字符串转换为 PyArray_Descr 结构体的函数定义
NPY_NO_EXPORT PyArray_Descr*
_descriptor_from_pep3118_format(char const *s)
{
char *buf, *p;
int in_name = 0;
int obtained;
PyObject *descr;
PyObject *str;
PyObject *_numpy_internal;
// 如果传入的 s 为 NULL,返回一个新的 NPY_BYTE 类型的 PyArray_Descr 结构体
if (s == NULL) {
return PyArray_DescrNewFromType(NPY_BYTE);
}
// 快速路径,尝试使用快速版本的 _descriptor_from_pep3118_format 函数
obtained = _descriptor_from_pep3118_format_fast(s, &descr);
if (obtained) {
return (PyArray_Descr*)descr;
}
// 去除 s 中的空白字符,但保留字段名中的空格
buf = malloc(strlen(s) + 1);
if (buf == NULL) {
PyErr_NoMemory();
return NULL;
}
p = buf;
while (*s != '\0') {
if (*s == ':') {
in_name = !in_name;
*p = *s;
p++;
}
else if (in_name || !NumPyOS_ascii_isspace(*s)) {
*p = *s;
p++;
}
s++;
}
*p = '\0';
// 根据处理后的 buf 创建 PyUnicode 对象
str = PyUnicode_FromStringAndSize(buf, strlen(buf));
// 如果创建失败,释放 buf 的内存并返回 NULL
if (str == NULL) {
free(buf);
return NULL;
}
// 导入 numpy._core._internal 模块
_numpy_internal = PyImport_ImportModule("numpy._core._internal");
# 如果 _numpy_internal 是 NULL,则说明未能获取到 numpy 内部对象,函数无法继续执行,返回 NULL
if (_numpy_internal == NULL) {
Py_DECREF(str); # 减少字符串对象的引用计数,避免内存泄漏
free(buf); # 释放 buf 所占用的内存空间
return NULL; # 返回 NULL 表示函数执行失败
}
# 调用 numpy 内部对象的方法 "_dtype_from_pep3118",将 str 作为参数传递
descr = PyObject_CallMethod(
_numpy_internal, "_dtype_from_pep3118", "O", str);
Py_DECREF(str); # 减少字符串对象的引用计数,避免内存泄漏
Py_DECREF(_numpy_internal); # 减少 numpy 内部对象的引用计数,避免内存泄漏
# 如果调用返回 NULL,说明处理失败,需要设置错误信息并返回 NULL
if (descr == NULL) {
PyObject *exc, *val, *tb; # 定义异常、值和 traceback 对象
PyErr_Fetch(&exc, &val, &tb); # 获取当前的错误信息
// 设置错误消息,指出 buf 不是一个有效的 PEP 3118 缓冲格式字符串
PyErr_Format(PyExc_ValueError,
"'%s' is not a valid PEP 3118 buffer format string", buf);
// 将当前异常链入上一个异常的原因中
npy_PyErr_ChainExceptionsCause(exc, val, tb);
free(buf); # 释放 buf 所占用的内存空间
return NULL; # 返回 NULL 表示函数执行失败
}
// 检查 descr 是否是一个有效的数组描述符对象,如果不是则抛出运行时错误
if (!PyArray_DescrCheck(descr)) {
// 设置运行时错误信息,指出 numpy._core._internal._dtype_from_pep3118 没有返回有效的数组描述符
PyErr_Format(PyExc_RuntimeError,
"internal error: numpy._core._internal._dtype_from_pep3118 "
"did not return a valid dtype, got %s", buf);
Py_DECREF(descr); // 减少描述符对象的引用计数,避免内存泄漏
free(buf); // 释放 buf 所占用的内存空间
return NULL; // 返回 NULL 表示函数执行失败
}
free(buf); // 释放 buf 所占用的内存空间
return (PyArray_Descr*)descr; // 返回有效的数组描述符对象的指针类型
/*
* Fast path for parsing buffer strings corresponding to simple types.
*
* Currently, this deals only with single-element data types.
*/
static int
_descriptor_from_pep3118_format_fast(char const *s, PyObject **result)
{
PyArray_Descr *descr;
int is_standard_size = 0;
char byte_order = '=';
int is_complex = 0;
int type_num = NPY_BYTE;
int item_seen = 0;
for (; *s != '\0'; ++s) {
is_complex = 0;
switch (*s) {
case '@':
case '^':
/* ^ means no alignment; doesn't matter for a single element */
byte_order = '=';
is_standard_size = 0;
break;
case '<':
byte_order = '<';
is_standard_size = 1;
break;
case '>':
case '!':
byte_order = '>';
is_standard_size = 1;
break;
case '=':
byte_order = '=';
is_standard_size = 1;
break;
case 'Z':
is_complex = 1;
++s;
default:
if (item_seen) {
/* Not a single-element data type */
return 0;
}
type_num = _pep3118_letter_to_type(*s, !is_standard_size,
is_complex);
if (type_num < 0) {
/* Something unknown */
return 0;
}
item_seen = 1;
break;
}
}
if (!item_seen) {
return 0;
}
descr = PyArray_DescrFromType(type_num);
if (descr == NULL) {
return 0;
}
if (byte_order == '=') {
*result = (PyObject*)descr;
}
else {
*result = (PyObject*)PyArray_DescrNewByteorder(descr, byte_order);
Py_DECREF(descr);
if (*result == NULL) {
return 0;
}
}
return 1;
}
static int
_pep3118_letter_to_type(char letter, int native, int is_complex)
{
switch (letter)
{
case '?': return NPY_BOOL;
case 'b': return NPY_BYTE;
case 'B': return NPY_UBYTE;
case 'h': return native ? NPY_SHORT : NPY_INT16;
case 'H': return native ? NPY_USHORT : NPY_UINT16;
case 'i': return native ? NPY_INT : NPY_INT32;
case 'I': return native ? NPY_UINT : NPY_UINT32;
case 'l': return native ? NPY_LONG : NPY_INT32;
case 'L': return native ? NPY_ULONG : NPY_UINT32;
case 'q': return native ? NPY_LONGLONG : NPY_INT64;
case 'Q': return native ? NPY_ULONGLONG : NPY_UINT64;
case 'n': return native ? NPY_INTP : -1;
case 'N': return native ? NPY_UINTP : -1;
case 'e': return NPY_HALF;
case 'f': return is_complex ? NPY_CFLOAT : NPY_FLOAT;
case 'd': return is_complex ? NPY_CDOUBLE : NPY_DOUBLE;
case 'g': return native ? (is_complex ? NPY_CLONGDOUBLE : NPY_LONGDOUBLE) : -1;
default:
/* Other unhandled cases */
return -1;
}
return -1;
}
.\numpy\numpy\_core\src\multiarray\calculation.c
// 定义宏,指定不使用已弃用的 NumPy API 版本
// 定义宏,指定编译器在链接时使用 multiarray 模块
// 定义宏,指定 Python 的 Py_ssize_t 类型使用“clean” API
// 包含 Python 核心头文件
// 包含结构成员相关的头文件
// 包含 NumPy 数组对象相关的头文件
// 包含低级步进循环的头文件
// 包含数据类型元信息的头文件
// 包含 NumPy 配置的头文件
// 包含通用功能的头文件
// 包含数字处理相关的头文件
// 包含计算相关的头文件
// 包含数组赋值相关的头文件
static double
power_of_ten(int n)
{
static const double p10[] = {1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8};
// 静态数组,包含 10 的幂次方值
double ret;
// 返回值
if (n < 9) {
// 如果 n 小于 9,则直接返回对应 p10 数组的值
ret = p10[n];
}
else {
// 如果 n 大于等于 9,则使用循环计算 10 的 n 次方
ret = 1e9;
while (n-- > 9) {
ret *= 10.;
}
}
return ret;
// 返回计算结果
}
NPY_NO_EXPORT PyObject *
_PyArray_ArgMinMaxCommon(PyArrayObject *op,
int axis, PyArrayObject *out, int keepdims,
npy_bool is_argmax)
{
PyArrayObject *ap = NULL, *rp = NULL;
// 定义 PyArrayObject 类型的指针变量 ap 和 rp
PyArray_ArgFunc* arg_func = NULL;
// 定义 PyArray_ArgFunc 类型的指针变量 arg_func
char *ip, *func_name;
// 定义字符指针变量 ip 和 func_name
npy_intp *rptr;
// 定义 npy_intp 类型的指针变量 rptr
npy_intp i, n, m;
// 定义 npy_intp 类型的变量 i, n, m
int elsize;
// 定义整型变量 elsize
// 保存 axis 的副本,因为后续调用 PyArray_CheckAxis 会改变它
int axis_copy = axis;
npy_intp _shape_buf[NPY_MAXDIMS];
// 定义长度为 NPY_MAXDIMS 的 npy_intp 类型数组 _shape_buf
npy_intp *out_shape;
// 定义 npy_intp 类型的指针变量 out_shape
// 保存原始数组的维度数和形状,当 keepdims 为 True 时有用
npy_intp* original_op_shape = PyArray_DIMS(op);
int out_ndim = PyArray_NDIM(op);
// 获取原始数组的维度数和形状
NPY_BEGIN_THREADS_DEF;
// 定义一个宏,用于线程控制,开始线程定义
if ((ap = (PyArrayObject *)PyArray_CheckAxis(op, &axis, 0)) == NULL) {
// 检查并获取经过轴处理后的数组 ap,如果失败则返回空指针
return NULL;
}
/*
* We need to permute the array so that axis is placed at the end.
* And all other dimensions are shifted left.
*/
// 我们需要重新排列数组,使得指定的 axis 放在最后一个位置,其他维度左移
if (axis != PyArray_NDIM(ap)-1) {
PyArray_Dims newaxes;
npy_intp dims[NPY_MAXDIMS];
int j;
newaxes.ptr = dims;
newaxes.len = PyArray_NDIM(ap);
for (j = 0; j < axis; j++) {
dims[j] = j;
}
for (j = axis; j < PyArray_NDIM(ap) - 1; j++) {
dims[j] = j + 1;
}
dims[PyArray_NDIM(ap) - 1] = axis;
// 对 ap 进行转置操作,得到新的数组 op
op = (PyArrayObject *)PyArray_Transpose(ap, &newaxes);
Py_DECREF(ap);
if (op == NULL) {
return NULL;
}
}
else {
op = ap;
}
// 获取原生字节顺序的连续副本
PyArray_Descr *descr = NPY_DT_CALL_ensure_canonical(PyArray_DESCR(op));
if (descr == NULL) {
return NULL;
}
// 从原始数组创建一个新的数组对象 ap
ap = (PyArrayObject *)PyArray_FromArray(op, descr, NPY_ARRAY_DEFAULT);
Py_DECREF(op);
if (ap == NULL) {
return NULL;
}
// 决定输出数组的形状
if (!keepdims) {
out_ndim = PyArray_NDIM(ap) - 1;
out_shape = PyArray_DIMS(ap);
}
else {
out_shape = _shape_buf;
// 如果需要,将输出形状初始化为形状缓冲区的内容
if (axis_copy == NPY_RAVEL_AXIS) {
// 如果轴复制标志为 NPY_RAVEL_AXIS,将输出形状的所有维度设为 1
for (int i = 0; i < out_ndim; i++) {
out_shape[i] = 1;
}
}
else {
/*
* 虽然 `ap` 可能已经转置,但是对于 `out` 来说我们可以忽略这一点,
* 因为转置仅重新排列大小为 1 的 `axis`(不改变内存布局)。
*/
// 复制原始操作形状到输出形状,除了指定的轴外,其它维度保持不变
memcpy(out_shape, original_op_shape, out_ndim * sizeof(npy_intp));
out_shape[axis] = 1;
}
}
// 如果是求取 argmax
if (is_argmax) {
// 设置函数名为 "argmax"
func_name = "argmax";
// 获取对应数据类型的 argmax 函数指针
arg_func = PyDataType_GetArrFuncs(PyArray_DESCR(ap))->argmax;
}
else {
// 设置函数名为 "argmin"
func_name = "argmin";
// 获取对应数据类型的 argmin 函数指针
arg_func = PyDataType_GetArrFuncs(PyArray_DESCR(ap))->argmin;
}
// 如果未找到合适的函数指针,抛出类型错误异常
if (arg_func == NULL) {
PyErr_SetString(PyExc_TypeError,
"data type not ordered");
goto fail;
}
// 计算元素大小(以字节为单位)
elsize = PyArray_ITEMSIZE(ap);
// 获取数组的最后一个维度大小
m = PyArray_DIMS(ap)[PyArray_NDIM(ap)-1];
// 如果最后一个维度大小为 0,则抛出值错误异常
if (m == 0) {
PyErr_Format(PyExc_ValueError,
"attempt to get %s of an empty sequence",
func_name);
goto fail;
}
// 如果输出对象为 NULL
if (!out) {
// 创建一个新的数组对象 rp,以 intp 类型的描述符和指定的输出形状
rp = (PyArrayObject *)PyArray_NewFromDescr(
Py_TYPE(ap), PyArray_DescrFromType(NPY_INTP),
out_ndim, out_shape, NULL, NULL,
0, (PyObject *)ap);
// 如果创建失败,则跳转到 fail 标签处理异常
if (rp == NULL) {
goto fail;
}
}
else {
// 如果输出对象不为 NULL,检查其维度和形状是否与预期匹配
if ((PyArray_NDIM(out) != out_ndim) ||
!PyArray_CompareLists(PyArray_DIMS(out), out_shape,
out_ndim)) {
PyErr_Format(PyExc_ValueError,
"output array does not match result of np.%s.",
func_name);
goto fail;
}
// 使用给定的数组对象 out 创建一个新的数组对象 rp,写入数据时若有副本则写回
rp = (PyArrayObject *)PyArray_FromArray(out,
PyArray_DescrFromType(NPY_INTP),
NPY_ARRAY_CARRAY | NPY_ARRAY_WRITEBACKIFCOPY);
// 如果创建失败,则跳转到 fail 标签处理异常
if (rp == NULL) {
goto fail;
}
}
// 开始线程保护,以数组对象 ap 的描述符
NPY_BEGIN_THREADS_DESCR(PyArray_DESCR(ap));
// 计算 ap 的总元素个数除以 m,得到 n 的值
n = PyArray_SIZE(ap)/m;
// 获取 rp 对象的数据指针
rptr = (npy_intp *)PyArray_DATA(rp);
// 遍历数组 ap 中的元素,每次处理 elsize*m 个元素
for (ip = PyArray_DATA(ap), i = 0; i < n; i++, ip += elsize*m) {
// 调用 arg_func 处理 ip 指向的数据,结果存入 rptr 指向的位置
arg_func(ip, m, rptr, ap);
// 移动 rptr 到下一个位置
rptr += 1;
}
// 结束线程保护,以数组对象 ap 的描述符
NPY_END_THREADS_DESCR(PyArray_DESCR(ap));
// 释放数组对象 ap 的引用
Py_DECREF(ap);
/* 如果需要,触发 WRITEBACKIFCOPY */
// 如果 out 不为 NULL 且 out 不等于 rp,则解析 WRITEBACKIFCOPY
if (out != NULL && out != rp) {
PyArray_ResolveWritebackIfCopy(rp);
// 释放 rp 的引用,将其设置为 out,并增加其引用计数
Py_DECREF(rp);
rp = out;
Py_INCREF(rp);
}
// 返回 rp 对象的 PyObject 指针形式
return (PyObject *)rp;
fail:
// 处理失败的情况:释放数组对象 ap 的引用,同时释放 rp 对象的引用
Py_DECREF(ap);
Py_XDECREF(rp);
// 返回 NULL 指针,表示发生异常
return NULL;
/*NUMPY_API
* ArgMaxWithKeepdims
*/
NPY_NO_EXPORT PyObject*
_PyArray_ArgMaxWithKeepdims(PyArrayObject *op,
int axis, PyArrayObject *out, int keepdims)
{
// 调用共同的最大值和最小值查找函数,返回最大值的索引,保持维度信息
return _PyArray_ArgMinMaxCommon(op, axis, out, keepdims, 1);
}
/*NUMPY_API
* ArgMax
*/
NPY_NO_EXPORT PyObject *
PyArray_ArgMax(PyArrayObject *op, int axis, PyArrayObject *out)
{
// 调用共同的最大值和最小值查找函数,返回最大值的索引,不保持维度信息
return _PyArray_ArgMinMaxCommon(op, axis, out, 0, 1);
}
/*NUMPY_API
* ArgMinWithKeepdims
*/
NPY_NO_EXPORT PyObject *
_PyArray_ArgMinWithKeepdims(PyArrayObject *op,
int axis, PyArrayObject *out, int keepdims)
{
// 调用共同的最大值和最小值查找函数,返回最小值的索引,保持维度信息
return _PyArray_ArgMinMaxCommon(op, axis, out, keepdims, 0);
}
/*NUMPY_API
* ArgMin
*/
NPY_NO_EXPORT PyObject *
PyArray_ArgMin(PyArrayObject *op, int axis, PyArrayObject *out)
{
// 调用共同的最大值和最小值查找函数,返回最小值的索引,不保持维度信息
return _PyArray_ArgMinMaxCommon(op, axis, out, 0, 0);
}
/*NUMPY_API
* Max
*/
NPY_NO_EXPORT PyObject *
PyArray_Max(PyArrayObject *ap, int axis, PyArrayObject *out)
{
PyArrayObject *arr;
PyObject *ret;
// 检查并调整轴,以确保它在合法范围内
arr = (PyArrayObject *)PyArray_CheckAxis(ap, &axis, 0);
if (arr == NULL) {
return NULL;
}
// 使用通用的规约函数找到数组中的最大值
ret = PyArray_GenericReduceFunction(arr, n_ops.maximum, axis,
PyArray_DESCR(arr)->type_num, out);
Py_DECREF(arr);
return ret;
}
/*NUMPY_API
* Min
*/
NPY_NO_EXPORT PyObject *
PyArray_Min(PyArrayObject *ap, int axis, PyArrayObject *out)
{
PyArrayObject *arr;
PyObject *ret;
// 检查并调整轴,以确保它在合法范围内
arr=(PyArrayObject *)PyArray_CheckAxis(ap, &axis, 0);
if (arr == NULL) {
return NULL;
}
// 使用通用的规约函数找到数组中的最小值
ret = PyArray_GenericReduceFunction(arr, n_ops.minimum, axis,
PyArray_DESCR(arr)->type_num, out);
Py_DECREF(arr);
return ret;
}
/*NUMPY_API
* Ptp (peak-to-peak)
*/
NPY_NO_EXPORT PyObject *
PyArray_Ptp(PyArrayObject *ap, int axis, PyArrayObject *out)
{
PyArrayObject *arr;
PyObject *ret;
PyObject *obj1 = NULL, *obj2 = NULL;
// 检查并调整轴,以确保它在合法范围内
arr=(PyArrayObject *)PyArray_CheckAxis(ap, &axis, 0);
if (arr == NULL) {
return NULL;
}
// 获取数组沿指定轴的最大值
obj1 = PyArray_Max(arr, axis, out);
if (obj1 == NULL) {
goto fail;
}
// 获取数组沿指定轴的最小值
obj2 = PyArray_Min(arr, axis, NULL);
if (obj2 == NULL) {
goto fail;
}
Py_DECREF(arr);
// 计算最大值和最小值的差值
if (out) {
ret = PyObject_CallFunction(n_ops.subtract, "OOO", out, obj2, out);
}
else {
ret = PyNumber_Subtract(obj1, obj2);
}
Py_DECREF(obj1);
Py_DECREF(obj2);
return ret;
fail:
Py_XDECREF(arr);
Py_XDECREF(obj1);
Py_XDECREF(obj2);
return NULL;
}
/*NUMPY_API
* Std (standard deviation)
*/
NPY_NO_EXPORT PyObject *
PyArray_Std(PyArrayObject *self, int axis, int rtype, PyArrayObject *out,
int variance)
{
// 调用内部函数,计算数组的标准差或方差
return __New_PyArray_Std(self, axis, rtype, out, variance, 0);
}
/* Helper function for PyArray_Std */
NPY_NO_EXPORT PyObject *
__New_PyArray_Std(PyArrayObject *self, int axis, int rtype, PyArrayObject *out,
int variance, int num)
{
PyObject *obj1 = NULL, *obj2 = NULL, *obj3 = NULL;
// 这里是实现标准差或方差计算的具体逻辑,未在这里注释
}
// 声明指向 NumPy 数组的指针,用于存储三个数组对象及一个返回对象
PyArrayObject *arr1 = NULL, *arr2 = NULL, *arrnew = NULL;
PyObject *ret = NULL, *newshape = NULL;
int i, n;
npy_intp val;
// 检查并返回 self 对象中指定轴的 PyArrayObject 对象
arrnew = (PyArrayObject *)PyArray_CheckAxis(self, &axis, 0);
if (arrnew == NULL) {
return NULL;
}
/* 计算并重塑均值 */
// 确保 arrnew 是一个数组对象,并计算其指定轴上的均值
arr1 = (PyArrayObject *)PyArray_EnsureAnyArray(
PyArray_Mean(arrnew, axis, rtype, NULL));
if (arr1 == NULL) {
Py_DECREF(arrnew);
return NULL;
}
// 获取 arrnew 的维度数,并创建一个新的元组 newshape
n = PyArray_NDIM(arrnew);
newshape = PyTuple_New(n);
if (newshape == NULL) {
Py_DECREF(arr1);
Py_DECREF(arrnew);
return NULL;
}
// 为 newshape 元组赋值,根据 arrnew 的维度设置新的形状
for (i = 0; i < n; i++) {
if (i == axis) {
val = 1;
}
else {
val = PyArray_DIM(arrnew,i);
}
PyTuple_SET_ITEM(newshape, i, PyLong_FromSsize_t(val));
}
// 根据 newshape 元组重塑 arr1 数组对象
arr2 = (PyArrayObject *)PyArray_Reshape(arr1, newshape);
Py_DECREF(arr1);
Py_DECREF(newshape);
if (arr2 == NULL) {
Py_DECREF(arrnew);
return NULL;
}
/* 计算 x = x - mx */
// 确保 arrnew 和 arr2 是数组对象,计算它们的减法
arr1 = (PyArrayObject *)PyArray_EnsureAnyArray(
PyNumber_Subtract((PyObject *)arrnew, (PyObject *)arr2));
Py_DECREF(arr2);
if (arr1 == NULL) {
Py_DECREF(arrnew);
return NULL;
}
/* 计算 x * x */
// 如果 arr1 是复数数组,取其共轭;否则直接增加其引用计数
if (PyArray_ISCOMPLEX(arr1)) {
obj3 = PyArray_Conjugate(arr1, NULL);
}
else {
obj3 = (PyObject *)arr1;
Py_INCREF(arr1);
}
if (obj3 == NULL) {
Py_DECREF(arrnew);
return NULL;
}
// 确保 arr1 和 obj3 是数组对象,使用通用的二进制函数计算它们的乘法
arr2 = (PyArrayObject *)PyArray_EnsureAnyArray(
PyArray_GenericBinaryFunction((PyObject *)arr1, obj3,
n_ops.multiply));
Py_DECREF(arr1);
Py_DECREF(obj3);
if (arr2 == NULL) {
Py_DECREF(arrnew);
return NULL;
}
// 如果 arr2 是复数数组,获取其实部;根据 rtype 设置新的数据类型
if (PyArray_ISCOMPLEX(arr2)) {
obj3 = PyObject_GetAttrString((PyObject *)arr2, "real");
switch(rtype) {
case NPY_CDOUBLE:
rtype = NPY_DOUBLE;
break;
case NPY_CFLOAT:
rtype = NPY_FLOAT;
break;
case NPY_CLONGDOUBLE:
rtype = NPY_LONGDOUBLE;
break;
}
}
else {
obj3 = (PyObject *)arr2;
Py_INCREF(arr2);
}
if (obj3 == NULL) {
Py_DECREF(arrnew);
return NULL;
}
/* 计算 add.reduce(x*x,axis) */
// 使用通用的减少函数计算 arr3(即 obj3)沿指定轴的加法
obj1 = PyArray_GenericReduceFunction((PyArrayObject *)obj3, n_ops.add,
axis, rtype, NULL);
Py_DECREF(obj3);
Py_DECREF(arr2);
if (obj1 == NULL) {
Py_DECREF(arrnew);
return NULL;
}
// 获取 arrnew 在指定轴上的维度,减去 num,如果结果为 0,则设置为 1
n = PyArray_DIM(arrnew,axis);
Py_DECREF(arrnew);
n = (n-num);
if (n == 0) {
n = 1;
}
// 创建一个新的 PyFloat 对象,表示计算结果的倒数
obj2 = PyFloat_FromDouble(1.0/((double )n));
if (obj2 == NULL) {
Py_DECREF(obj1);
return NULL;
}
// 计算最终的结果,obj1 乘以 obj2
ret = PyNumber_Multiply(obj1, obj2);
// 减少对象 obj1 的引用计数
Py_DECREF(obj1);
// 减少对象 obj2 的引用计数
Py_DECREF(obj2);
// 如果方差为假值(0或NULL),执行以下代码块
if (!variance) {
// 将返回值 ret 转换为 PyArrayObject 对象
arr1 = (PyArrayObject *)PyArray_EnsureAnyArray(ret);
/* 对返回值进行平方根运算 */
ret = PyArray_GenericUnaryFunction(arr1, n_ops.sqrt);
// 减少 arr1 对象的引用计数
Py_DECREF(arr1);
}
// 如果返回值 ret 为 NULL,则返回 NULL
if (ret == NULL) {
return NULL;
}
// 如果 self 是一个确切的 PyArray 对象,跳转到 finish 标签
if (PyArray_CheckExact(self)) {
goto finish;
}
// 如果 self 是 PyArray 对象,并且 self 和 ret 的类型相同,跳转到 finish 标签
if (PyArray_Check(self) && Py_TYPE(self) == Py_TYPE(ret)) {
goto finish;
}
// 将返回值 ret 转换为 PyArrayObject 对象
arr1 = (PyArrayObject *)PyArray_EnsureArray(ret);
// 如果 arr1 为 NULL,则返回 NULL
if (arr1 == NULL) {
return NULL;
}
// 将 arr1 转换为一个以 self 类型为基础的视图,返回值赋给 ret
ret = PyArray_View(arr1, NULL, Py_TYPE(self));
// 减少 arr1 对象的引用计数
Py_DECREF(arr1);
/*NUMPY_API
* Round
*/
NPY_NO_EXPORT PyObject *
PyArray_Round(PyArrayObject *a, int decimals, PyArrayObject *out)
{
PyObject *f, *ret = NULL, *tmp, *op1, *op2;
int ret_int=0;
PyArray_Descr *my_descr;
// 如果提供了输出数组 out,并且其形状与输入数组 a 不匹配,则报错并返回 NULL
if (out && (PyArray_SIZE(out) != PyArray_SIZE(a))) {
PyErr_SetString(PyExc_ValueError,
"invalid output shape");
return NULL;
}
// ...以下代码省略,未提供的部分不需要添加注释
}
if (PyArray_ISCOMPLEX(a)) {
PyObject *part;
PyObject *round_part;
PyObject *arr;
int res;
if (out) {
arr = (PyObject *)out;
Py_INCREF(arr);
}
else {
arr = PyArray_Copy(a);
if (arr == NULL) {
return NULL;
}
}
/* arr.real = a.real.round(decimals) */
part = PyObject_GetAttrString((PyObject *)a, "real");
if (part == NULL) {
Py_DECREF(arr);
return NULL;
}
part = PyArray_EnsureAnyArray(part);
round_part = PyArray_Round((PyArrayObject *)part,
decimals, NULL);
Py_DECREF(part);
if (round_part == NULL) {
Py_DECREF(arr);
return NULL;
}
res = PyObject_SetAttrString(arr, "real", round_part);
Py_DECREF(round_part);
if (res < 0) {
Py_DECREF(arr);
return NULL;
}
/* arr.imag = a.imag.round(decimals) */
part = PyObject_GetAttrString((PyObject *)a, "imag");
if (part == NULL) {
Py_DECREF(arr);
return NULL;
}
part = PyArray_EnsureAnyArray(part);
round_part = PyArray_Round((PyArrayObject *)part,
decimals, NULL);
Py_DECREF(part);
if (round_part == NULL) {
Py_DECREF(arr);
return NULL;
}
res = PyObject_SetAttrString(arr, "imag", round_part);
Py_DECREF(round_part);
if (res < 0) {
Py_DECREF(arr);
return NULL;
}
return arr;
}
/* do the most common case first */
if (decimals >= 0) {
if (PyArray_ISINTEGER(a)) {
if (out) {
if (PyArray_AssignArray(out, a,
NULL, NPY_DEFAULT_ASSIGN_CASTING) < 0) {
return NULL;
}
Py_INCREF(out);
return (PyObject *)out;
}
else {
Py_INCREF(a);
return (PyObject *)a;
}
}
if (decimals == 0) {
if (out) {
return PyObject_CallFunction(n_ops.rint, "OO", a, out);
}
return PyObject_CallFunction(n_ops.rint, "O", a);
}
op1 = n_ops.multiply;
op2 = n_ops.true_divide;
}
else {
op1 = n_ops.true_divide;
op2 = n_ops.multiply;
decimals = -decimals;
}
// 如果输出对象 out 为空,则根据输入数组 a 的类型决定如何处理
if (!out) {
// 如果输入数组 a 是整数类型,则设置返回整数标志并创建双精度浮点数类型描述符
if (PyArray_ISINTEGER(a)) {
ret_int = 1;
my_descr = PyArray_DescrFromType(NPY_DOUBLE);
}
else {
// 否则,增加输入数组 a 的描述符的引用计数,并将其作为描述符
Py_INCREF(PyArray_DESCR(a));
my_descr = PyArray_DESCR(a);
}
// 使用数组 a 的维度和描述符创建一个空的数组对象 out
out = (PyArrayObject *)PyArray_Empty(PyArray_NDIM(a), PyArray_DIMS(a),
my_descr,
PyArray_ISFORTRAN(a));
// 如果创建出错,返回空指针
if (out == NULL) {
return NULL;
}
}
else {
// 如果输出对象不为空,增加其引用计数
Py_INCREF(out);
}
// 根据给定的小数位数创建一个 Python 浮点数对象 f
f = PyFloat_FromDouble(power_of_ten(decimals));
// 如果创建出错,返回空指针
if (f == NULL) {
return NULL;
}
// 调用指定函数 op1,传递数组 a、浮点数 f 和输出对象 out 作为参数
ret = PyObject_CallFunction(op1, "OOO", a, f, out);
// 如果调用出错,跳转至清理代码块 finish
if (ret == NULL) {
goto finish;
}
// 调用函数 n_ops.rint,传递 ret 作为参数,执行四舍五入操作
tmp = PyObject_CallFunction(n_ops.rint, "OO", ret, ret);
// 如果调用出错,释放 ret 并跳转至清理代码块 finish
if (tmp == NULL) {
Py_DECREF(ret);
ret = NULL;
goto finish;
}
// 释放 tmp 对象
Py_DECREF(tmp);
// 再次调用指定函数 op2,传递 ret、f 和 ret 作为参数
tmp = PyObject_CallFunction(op2, "OOO", ret, f, ret);
// 如果调用出错,释放 ret 并跳转至清理代码块 finish
if (tmp == NULL) {
Py_DECREF(ret);
ret = NULL;
goto finish;
}
// 释放 tmp 对象
Py_DECREF(tmp);
finish:
// 释放浮点数对象 f
Py_DECREF(f);
// 释放输出对象 out
Py_DECREF(out);
// 如果返回整数标志为真且 ret 不为空,则将 ret 转换为输入数组 a 的类型并返回
if (ret_int && ret != NULL) {
Py_INCREF(PyArray_DESCR(a));
tmp = PyArray_CastToType((PyArrayObject *)ret,
PyArray_DESCR(a), PyArray_ISFORTRAN(a));
Py_DECREF(ret);
return tmp;
}
// 返回 ret 对象
return ret;
/*NUMPY_API
* Mean
*/
NPY_NO_EXPORT PyObject *
PyArray_Mean(PyArrayObject *self, int axis, int rtype, PyArrayObject *out)
{
PyObject *obj1 = NULL, *obj2 = NULL, *ret;
PyArrayObject *arr;
// 检查并调整轴向,确保数组符合要求
arr = (PyArrayObject *)PyArray_CheckAxis(self, &axis, 0);
if (arr == NULL) {
return NULL;
}
// 对数组进行通用约简操作,使用加法操作
obj1 = PyArray_GenericReduceFunction(arr, n_ops.add, axis,
rtype, out);
// 创建一个包含数组指定轴向维度的浮点数对象
obj2 = PyFloat_FromDouble((double)PyArray_DIM(arr,axis));
Py_DECREF(arr);
// 检查对象是否创建成功,否则清理并返回空
if (obj1 == NULL || obj2 == NULL) {
Py_XDECREF(obj1);
Py_XDECREF(obj2);
return NULL;
}
// 如果没有指定输出对象,则进行真实除法操作
if (!out) {
ret = PyNumber_TrueDivide(obj1, obj2);
}
// 否则,调用分裂函数进行除法操作
else {
ret = PyObject_CallFunction(n_ops.divide, "OOO", out, obj2, out);
}
Py_DECREF(obj1);
Py_DECREF(obj2);
return ret;
}
/*NUMPY_API
* Any
*/
NPY_NO_EXPORT PyObject *
PyArray_Any(PyArrayObject *self, int axis, PyArrayObject *out)
{
PyObject *arr, *ret;
// 检查并调整轴向,确保数组符合要求
arr = PyArray_CheckAxis(self, &axis, 0);
if (arr == NULL) {
return NULL;
}
// 对数组进行通用约简操作,使用逻辑或操作
ret = PyArray_GenericReduceFunction((PyArrayObject *)arr,
n_ops.logical_or, axis,
NPY_BOOL, out);
Py_DECREF(arr);
return ret;
}
/*NUMPY_API
* All
*/
NPY_NO_EXPORT PyObject *
PyArray_All(PyArrayObject *self, int axis, PyArrayObject *out)
{
PyObject *arr, *ret;
// 检查并调整轴向,确保数组符合要求
arr = PyArray_CheckAxis(self, &axis, 0);
if (arr == NULL) {
return NULL;
}
// 对数组进行通用约简操作,使用逻辑与操作
ret = PyArray_GenericReduceFunction((PyArrayObject *)arr,
n_ops.logical_and, axis,
NPY_BOOL, out);
Py_DECREF(arr);
return ret;
}
/*NUMPY_API
* Clip
*/
NPY_NO_EXPORT PyObject *
PyArray_Clip(PyArrayObject *self, PyObject *min, PyObject *max, PyArrayObject *out)
{
// 将 None 视为 NULL 处理
if (min == Py_None) {
min = NULL;
}
if (max == Py_None) {
max = NULL;
}
// 必须设置 max 或 min,否则报错
if ((max == NULL) && (min == NULL)) {
PyErr_SetString(PyExc_ValueError,
"array_clip: must set either max or min");
return NULL;
}
// 根据条件调用对应的最小值或最大值函数,或者调用剪裁函数
if (min == NULL) {
return PyObject_CallFunctionObjArgs(n_ops.minimum, self, max, out, NULL);
}
else if (max == NULL) {
return PyObject_CallFunctionObjArgs(n_ops.maximum, self, min, out, NULL);
}
else {
return PyObject_CallFunctionObjArgs(n_ops.clip, self, min, max, out, NULL);
}
}
/*NUMPY_API
* Conjugate
*/
NPY_NO_EXPORT PyObject *
PyArray_Conjugate(PyArrayObject *self, PyArrayObject *out)
{
// TO DO: Implement conjugation function
// 尚未实现的共轭函数
return NULL;
}
if (PyArray_ISCOMPLEX(self) || PyArray_ISOBJECT(self) ||
PyArray_ISUSERDEF(self)) {
if (out == NULL) {
return PyArray_GenericUnaryFunction(self,
n_ops.conjugate);
}
else {
return PyArray_GenericBinaryFunction((PyObject *)self,
(PyObject *)out,
n_ops.conjugate);
}
}
else {
PyArrayObject *ret;
if (!PyArray_ISNUMBER(self)) {
/* 2017-05-04, 1.13 */
if (DEPRECATE("attempting to conjugate non-numeric dtype; this "
"will error in the future to match the behavior of "
"np.conjugate") < 0) {
return NULL;
}
}
if (out) {
if (PyArray_AssignArray(out, self,
NULL, NPY_DEFAULT_ASSIGN_CASTING) < 0) {
return NULL;
}
ret = out;
}
else {
ret = self;
}
Py_INCREF(ret);
return (PyObject *)ret;
}
/*NUMPY_API
* Trace
*/
/* 定义一个不导出的函数 PyArray_Trace,接受一个 PyArrayObject 类型的参数 self,
* 以及几个整型参数 offset, axis1, axis2, rtype 和一个 PyArrayObject 类型的参数 out。
* 返回一个 PyObject 指针。
*/
NPY_NO_EXPORT PyObject *
PyArray_Trace(PyArrayObject *self, int offset, int axis1, int axis2,
int rtype, PyArrayObject *out)
{
PyObject *diag = NULL, *ret = NULL;
/* 调用 PyArray_Diagonal 函数,传入 self, offset, axis1, axis2 参数,返回值赋给 diag */
diag = PyArray_Diagonal(self, offset, axis1, axis2);
/* 如果 diag 为 NULL,则直接返回 NULL */
if (diag == NULL) {
return NULL;
}
/* 调用 PyArray_GenericReduceFunction 函数,传入 diag, n_ops.add, -1, rtype, out 参数,
* 返回值赋给 ret
*/
ret = PyArray_GenericReduceFunction((PyArrayObject *)diag, n_ops.add, -1, rtype, out);
/* 减少 diag 的引用计数 */
Py_DECREF(diag);
/* 返回 ret 指针 */
return ret;
}
.\numpy\numpy\_core\src\multiarray\calculation.h
// 声明不导出的函数,用于计算数组中的最大值索引,支持指定轴和输出对象
NPY_NO_EXPORT PyObject*
PyArray_ArgMax(PyArrayObject* self, int axis, PyArrayObject *out);
// 声明不导出的函数,用于计算数组中的最大值索引,支持指定轴、输出对象和保持维度标志
NPY_NO_EXPORT PyObject*
_PyArray_ArgMaxWithKeepdims(PyArrayObject* self, int axis, PyArrayObject *out, int keepdims);
// 声明不导出的函数,用于计算数组中的最小值索引,支持指定轴和输出对象
NPY_NO_EXPORT PyObject*
PyArray_ArgMin(PyArrayObject* self, int axis, PyArrayObject *out);
// 声明不导出的函数,用于计算数组中的最小值索引,支持指定轴、输出对象和保持维度标志
NPY_NO_EXPORT PyObject*
_PyArray_ArgMinWithKeepdims(PyArrayObject* self, int axis, PyArrayObject *out, int keepdims);
// 声明不导出的函数,用于计算数组中的最大值,支持指定轴和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Max(PyArrayObject* self, int axis, PyArrayObject* out);
// 声明不导出的函数,用于计算数组中的最小值,支持指定轴和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Min(PyArrayObject* self, int axis, PyArrayObject* out);
// 声明不导出的函数,用于计算数组中的峰峰值(最大值与最小值之差),支持指定轴和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Ptp(PyArrayObject* self, int axis, PyArrayObject* out);
// 声明不导出的函数,用于计算数组中的均值,支持指定轴、返回类型和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Mean(PyArrayObject* self, int axis, int rtype, PyArrayObject* out);
// 声明不导出的函数,用于对数组进行四舍五入,支持指定小数位数和输出对象
NPY_NO_EXPORT PyObject *
PyArray_Round(PyArrayObject *a, int decimals, PyArrayObject *out);
// 声明不导出的函数,用于计算数组中的迹(对角线元素之和),支持指定偏移、轴和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Trace(PyArrayObject* self, int offset, int axis1, int axis2,
int rtype, PyArrayObject* out);
// 声明不导出的函数,用于裁剪数组,将元素限制在指定范围内,支持最小值、最大值和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Clip(PyArrayObject* self, PyObject* min, PyObject* max, PyArrayObject *out);
// 声明不导出的函数,用于对数组进行共轭操作,支持输出对象
NPY_NO_EXPORT PyObject*
PyArray_Conjugate(PyArrayObject* self, PyArrayObject* out);
// 声明不导出的函数,用于对数组进行四舍五入,支持指定小数位数和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Round(PyArrayObject* self, int decimals, PyArrayObject* out);
// 声明不导出的函数,用于计算数组中的标准差,支持指定轴、返回类型、输出对象和方差标志
NPY_NO_EXPORT PyObject*
PyArray_Std(PyArrayObject* self, int axis, int rtype, PyArrayObject* out,
int variance);
// 声明不导出的函数,用于计算数组中的标准差,支持指定轴、返回类型、输出对象、方差标志和数值
NPY_NO_EXPORT PyObject *
__New_PyArray_Std(PyArrayObject *self, int axis, int rtype, PyArrayObject *out,
int variance, int num);
// 声明不导出的函数,用于计算数组中的总和,支持指定轴、返回类型和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Sum(PyArrayObject* self, int axis, int rtype, PyArrayObject* out);
// 声明不导出的函数,用于计算数组的累积和,支持指定轴、返回类型和输出对象
NPY_NO_EXPORT PyObject*
PyArray_CumSum(PyArrayObject* self, int axis, int rtype, PyArrayObject* out);
// 声明不导出的函数,用于计算数组中的乘积,支持指定轴、返回类型和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Prod(PyArrayObject* self, int axis, int rtype, PyArrayObject* out);
// 声明不导出的函数,用于计算数组的累积乘积,支持指定轴、返回类型和输出对象
NPY_NO_EXPORT PyObject*
PyArray_CumProd(PyArrayObject* self, int axis, int rtype, PyArrayObject* out);
// 声明不导出的函数,用于判断数组中所有元素是否都为真,支持指定轴和输出对象
NPY_NO_EXPORT PyObject*
PyArray_All(PyArrayObject* self, int axis, PyArrayObject* out);
// 声明不导出的函数,用于判断数组中是否有任一元素为真,支持指定轴和输出对象
NPY_NO_EXPORT PyObject*
PyArray_Any(PyArrayObject* self, int axis, PyArrayObject* out);
.\numpy\numpy\_core\src\multiarray\can_cast_table.h
/*
* This file defines a compile time constant casting table for use in
* a few situations:
* 1. As a fast-path in can-cast (untested how much it helps).
* 2. To define the actual cast safety stored on the CastingImpl/ArrayMethod
* 3. For scalar math, since it also needs cast safety information.
*
* It is useful to have this constant to allow writing compile time generic
* code based on cast safety in the scalar math code.
*/
/* The from type fits into to (it has a smaller or equal number of bits) */
/* Unsigned "from" fits a signed integer if it is truly smaller */
/* Integer "from" only fits a float if it is truly smaller or double... */
NPY_SIZEOF_
NPY_SIZEOF_
&& NPY_SIZEOF_
/*
* NOTE: The Order is bool, integers (signed, unsigned) tuples, float, cfloat,
* then 6 fixed ones (object, string, unicode, void, datetime, timedelta),
* and finally half.
* Note that in the future we may only need the numeric casts here, but
* currently it fills in the others as well.
*/
{0, \
UFITS(FROM, BYTE), FITS(FROM, BYTE), UFITS(FROM, SHORT), FITS(FROM, SHORT), \
UFITS(FROM, INT), FITS(FROM, INT), UFITS(FROM, LONG), FITS(FROM, LONG), \
UFITS(FROM, LONGLONG), FITS(FROM, LONGLONG), \
IFITS(FROM, FLOAT), IFITS(FROM, DOUBLE), IFITS(FROM, LONGDOUBLE), \
IFITS(FROM, FLOAT), IFITS(FROM, DOUBLE), IFITS(FROM, LONGDOUBLE), \
1, 1, 1, 1, 0, NPY_SIZEOF_
{0, \
FITS(FROM, BYTE), 0, FITS(FROM, SHORT), 0, \
FITS(FROM, INT), 0, FITS(FROM, LONG), 0, \
FITS(FROM, LONGLONG), 0, \
IFITS(FROM, FLOAT), IFITS(FROM, DOUBLE), IFITS(FROM, LONGDOUBLE), \
IFITS(FROM, FLOAT), IFITS(FROM, DOUBLE), IFITS(FROM, LONGDOUBLE), \
1, 1, 1, 1, 0, NPY_SIZEOF_
/* Floats are similar to ints, but cap at double */
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
FITS(FROM, FLOAT), FITS(FROM, DOUBLE), FITS(FROM, LONGDOUBLE), \
FITS(FROM, FLOAT), FITS(FROM, DOUBLE), FITS(FROM, LONGDOUBLE), \
1, 1, 1, 1, 0, 0, FITS(FROM, HALF)}
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
0, 0, 0, \
FITS(FROM, FLOAT), FITS(FROM, DOUBLE), FITS(FROM, LONGDOUBLE), \
1, 1, 1, 1, 0, 0, 0}
注释:
/*
* 安全类型转换表格 `_npy_can_cast_safely_table`
* 这个表格描述了NumPy中每种数据类型之间的安全转换关系。
* 表格的行和列代表不同的数据类型,每个元素指示从行类型到列类型的转换是否安全。
*/
static const npy_bool _npy_can_cast_safely_table[NPY_NTYPES_LEGACY][NPY_NTYPES_LEGACY] = {
/* Bool 安全转换到除了 datetime(没有零值)之外的任何类型 */
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 1, 1},
/* 整数类型,分为有符号和无符号 */
CASTS_SAFELY_FROM_INT(BYTE), CASTS_SAFELY_FROM_UINT(BYTE),
CASTS_SAFELY_FROM_INT(SHORT), CASTS_SAFELY_FROM_UINT(SHORT),
CASTS_SAFELY_FROM_INT(INT), CASTS_SAFELY_FROM_UINT(INT),
CASTS_SAFELY_FROM_INT(LONG), CASTS_SAFELY_FROM_UINT(LONG),
CASTS_SAFELY_FROM_INT(LONGLONG), CASTS_SAFELY_FROM_UINT(LONGLONG),
/* 浮点数和复数 */
CASTS_SAFELY_FROM_FLOAT(FLOAT),
CASTS_SAFELY_FROM_FLOAT(DOUBLE),
CASTS_SAFELY_FROM_FLOAT(LONGDOUBLE),
CASTS_SAFELY_FROM_CFLOAT(FLOAT),
CASTS_SAFELY_FROM_CFLOAT(DOUBLE),
CASTS_SAFELY_FROM_CFLOAT(LONGDOUBLE),
/*
* 主要的数值类型后面是:
* object, string, unicode, void, datetime, timedelta (以及 half)
*/
/* object 类型只能安全转换到它自己 */
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* bool + ints */
0, 0, 0, 0, 0, 0, /* floats (without half) */
1, 0, 0, 0, 0, 0, 0},
/* string 类型可以安全转换到 object, unicode 和 void */
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* bool + ints */
0, 0, 0, 0, 0, 0, /* floats (without half) */
1, 1, 1, 1, 0, 0, 0},
/* unicode 类型可以安全转换到 object 和 void */
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* bool + ints */
0, 0, 0, 0, 0, 0, /* floats (without half) */
1, 0, 1, 1, 0, 0, 0},
/* void 类型可以安全转换到 object */
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* bool + ints */
0, 0, 0, 0, 0, 0, /* floats (without half) */
1, 0, 0, 1, 0, 0, 0},
/* datetime 类型可以安全转换到 object, string, unicode, void */
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* bool + ints */
0, 0, 0, 0, 0, 0, /* floats (without half) */
1, 1, 1, 1, 1, 0, 0},
/* timedelta 类型可以安全转换到 object, string, unicode, void */
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* bool + ints */
0, 0, 0, 0, 0, 0, /* floats (without half) */
1, 1, 1, 1, 0, 1, 0},
/* half 类型 */
CASTS_SAFELY_FROM_FLOAT(HALF),
};
.\numpy\numpy\_core\src\multiarray\common.c
/*
* Define NPY_NO_DEPRECATED_API to use the latest NumPy API version.
* Define _MULTIARRAYMODULE to indicate this module includes multi-array support.
*/
/*
* Ensure PY_SSIZE_T_CLEAN is defined before including Python.h
* to use the new Py_ssize_t based API for Python objects.
*/
/*
* The casting to use for implicit assignment operations resulting from
* in-place operations (like +=) and out= arguments. (Notice that this
* variable is misnamed, but it's part of the public API so I'm not sure we
* can just change it. Maybe someone should try and see if anyone notices.
*/
/*
* In numpy 1.6 and earlier, this was NPY_UNSAFE_CASTING. In a future
* release, it will become NPY_SAME_KIND_CASTING. Right now, during the
* transitional period, we continue to follow the NPY_UNSAFE_CASTING rules (to
* avoid breaking people's code), but we also check for whether the cast would
* be allowed under the NPY_SAME_KIND_CASTING rules, and if not we issue a
* warning (that people's code will be broken in a future release.)
*/
// Set the default casting rule for assignment operations
NPY_NO_EXPORT NPY_CASTING NPY_DEFAULT_ASSIGN_CASTING = NPY_SAME_KIND_CASTING;
// Function to find a NumPy dtype corresponding to a Python scalar object
NPY_NO_EXPORT PyArray_Descr *
_array_find_python_scalar_type(PyObject *op)
{
// Check if the Python object is a float
if (PyFloat_Check(op)) {
// Return the NumPy dtype descriptor for double precision floating point
return PyArray_DescrFromType(NPY_DOUBLE);
}
// Check if the Python object is a complex number
else if (PyComplex_Check(op)) {
// Return the NumPy dtype descriptor for double precision complex
return PyArray_DescrFromType(NPY_CDOUBLE);
}
// Check if the Python object is a long integer
else if (PyLong_Check(op)) {
// Return the NumPy dtype descriptor discovered from the Python long object
return NPY_DT_CALL_discover_descr_from_pyobject(
&PyArray_PyLongDType, op);
}
// If the object doesn't match any of the above types, return NULL
return NULL;
}
/*
* Get a suitable string dtype by calling `__str__`.
* For `np.bytes_`, this assumes an ASCII encoding.
*/
NPY_NO_EXPORT PyArray_Descr *
PyArray_DTypeFromObjectStringDiscovery(
PyObject *obj, PyArray_Descr *last_dtype, int string_type)
{
int itemsize;
// If the string type is byte string (NPY_STRING)
if (string_type == NPY_STRING) {
// Get a string representation of the object
PyObject *temp = PyObject_Str(obj);
if (temp == NULL) {
return NULL;
}
// Calculate the length of the string in characters
itemsize = PyUnicode_GetLength(temp);
Py_DECREF(temp);
if (itemsize < 0) {
return NULL;
}
}
// If the string type is Unicode string (NPY_UNICODE)
else if (string_type == NPY_UNICODE) {
// Get a string representation of the object
PyObject *temp = PyObject_Str(obj);
if (temp == NULL) {
return NULL;
}
// Calculate the length of the string in characters
itemsize = PyUnicode_GetLength(temp);
Py_DECREF(temp);
if (itemsize < 0) {
return NULL;
}
// Convert UCS4 codepoints to bytes (UCS4 = 4 bytes per character)
itemsize *= 4;
}
else {
// If the string type is neither NPY_STRING nor NPY_UNICODE, return NULL
return NULL;
}
// If the last dtype is provided and matches the current string type and size
if (last_dtype != NULL &&
last_dtype->type_num == string_type &&
last_dtype->elsize >= itemsize) {
// Return the existing dtype with incremented reference count
Py_INCREF(last_dtype);
return last_dtype;
}
// Create a new dtype descriptor for the specified string type
PyArray_Descr *dtype = PyArray_DescrNewFromType(string_type);
if (dtype == NULL) {
return NULL;
}
// Set the size of the dtype to match the calculated itemsize
dtype->elsize = itemsize;
return dtype;
}
/*
* This function extracts the dtype from a Python object and performs shape discovery.
* It returns only the dtype and is intended to be phased out in favor of PyArray_DiscoverDTypeAndShape.
* This function is exported from the NumPy C API and should be used with caution.
*/
NPY_NO_EXPORT int
PyArray_DTypeFromObject(PyObject *obj, int maxdims, PyArray_Descr **out_dtype)
{
coercion_cache_obj *cache = NULL; // Initialize coercion cache pointer
npy_intp shape[NPY_MAXDIMS]; // Array to store shape information
int ndim; // Variable to hold number of dimensions
// Call PyArray_DiscoverDTypeAndShape to discover dtype and shape of the object
ndim = PyArray_DiscoverDTypeAndShape(
obj, maxdims, shape, &cache, NULL, NULL, out_dtype, 1, NULL);
if (ndim < 0) {
return -1; // Return -1 on error
}
npy_free_coercion_cache(cache); // Free coercion cache memory
return 0; // Return 0 indicating success
}
NPY_NO_EXPORT npy_bool
_IsWriteable(PyArrayObject *ap)
{
PyObject *base = PyArray_BASE(ap); // Get the base object of the array
Py_buffer view; // Buffer view object for array data
/*
* Check if the array is writable based on its base and ownership flags.
* Arrays without a base or with owned data are considered writable.
*/
if (base == NULL || PyArray_CHKFLAGS(ap, NPY_ARRAY_OWNDATA)) {
/*
* Handle cases where arrays wrapped in C-data may not own their data,
* or where WRITEBACKIFCOPY arrays own their data but have a base.
*/
return NPY_TRUE; // Return true indicating array is writable
}
/*
* Traverse through the base objects to find the final base.
* If a writable array is found during traversal, return true.
*/
while (PyArray_Check(base)) {
ap = (PyArrayObject *)base; // Cast base to PyArrayObject
base = PyArray_BASE(ap); // Update base to next base object
if (PyArray_ISWRITEABLE(ap)) {
/*
* If any base is writable, return true.
* Bases are typically collapsed to the most general one.
*/
return NPY_TRUE;
}
if (base == NULL || PyArray_CHKFLAGS(ap, NPY_ARRAY_OWNDATA)) {
/* No further base to test for writeability */
return NPY_FALSE;
}
assert(!PyArray_CHKFLAGS(ap, NPY_ARRAY_OWNDATA)); // Assert ownership flag is not set
}
// Check if the base object supports a writable buffer view
if (PyObject_GetBuffer(base, &view, PyBUF_WRITABLE|PyBUF_SIMPLE) < 0) {
PyErr_Clear(); // Clear any raised Python exceptions
return NPY_FALSE; // Return false if buffer view cannot be obtained
}
PyBuffer_Release(&view); // Release the buffer view
return NPY_TRUE; // Return true indicating array is writable
}
/**
* Convert an array shape to a string representation such as "(1, 2)".
*
* @param n - Dimensionality of the shape
* @param vals - Pointer to shape array
* @param ending - String to append after the shape "(1, 2)%s"
*
* @return Python unicode string object representing the shape
*/
NPY_NO_EXPORT PyObject *
convert_shape_to_string(npy_intp n, npy_intp const *vals, char *ending)
{
npy_intp i; // Loop variable
/*
* Convert the array shape into a string representation like "(1, 2)".
* Append the specified ending string to the formatted shape.
*/
* Convert an array shape to a string representation such as "(1, 2)".
*
* @param n - Dimensionality of the shape
* @param vals - Pointer to shape array
* @param ending - String to append after the shape "(1, 2)%s"
*
* @return Python unicode string object representing the shape
*/
NPY_NO_EXPORT PyObject *
convert_shape_to_string(npy_intp n, npy_intp const *vals, char *ending)
{
npy_intp i; // Loop variable
/*
* Convert the array shape into a string representation like "(1, 2)".
* Append the specified ending string to the formatted shape.
*/
/*
* 如果值为负数,表示 "newaxis" 维度,对于打印来说可以丢弃,
* 如果它是第一个维度。找到第一个非 "newaxis" 维度。
*/
for (i = 0; i < n && vals[i] < 0; i++);
// 如果所有维度都是 "newaxis"
if (i == n) {
// 返回一个格式化的空元组字符串
return PyUnicode_FromFormat("()%s", ending);
}
// 创建一个字符串对象,表示第一个非 "newaxis" 维度
PyObject *ret = PyUnicode_FromFormat("%" NPY_INTP_FMT, vals[i++]);
if (ret == NULL) {
return NULL;
}
// 处理剩余的维度
for (; i < n; ++i) {
PyObject *tmp;
// 如果维度是 "newaxis"
if (vals[i] < 0) {
tmp = PyUnicode_FromString(",newaxis");
}
else {
// 否则创建一个表示维度值的字符串对象
tmp = PyUnicode_FromFormat(",%" NPY_INTP_FMT, vals[i]);
}
if (tmp == NULL) {
Py_DECREF(ret);
return NULL;
}
// 将当前维度字符串与之前的字符串连接起来
Py_SETREF(ret, PyUnicode_Concat(ret, tmp));
Py_DECREF(tmp);
if (ret == NULL) {
return NULL;
}
}
// 最后格式化结果字符串,根据是否有多个维度选择不同的格式
if (i == 1) {
Py_SETREF(ret, PyUnicode_FromFormat("(%S,)%s", ret, ending));
}
else {
Py_SETREF(ret, PyUnicode_FromFormat("(%S)%s", ret, ending));
}
return ret;
/**
* dot_alignment_error - 用于报告数组形状不匹配错误的函数
*
* @param a: 第一个数组对象
* @param i: 第一个数组中导致错误的维度索引
* @param b: 第二个数组对象
* @param j: 第二个数组中导致错误的维度索引
*/
NPY_NO_EXPORT void
dot_alignment_error(PyArrayObject *a, int i, PyArrayObject *b, int j)
{
PyObject *errmsg = NULL, *format = NULL, *fmt_args = NULL,
*i_obj = NULL, *j_obj = NULL,
*shape1 = NULL, *shape2 = NULL,
*shape1_i = NULL, *shape2_j = NULL;
// 构建错误消息的格式字符串
format = PyUnicode_FromString("shapes %s and %s not aligned:"
" %d (dim %d) != %d (dim %d)");
// 将数组形状转换为字符串表示
shape1 = convert_shape_to_string(PyArray_NDIM(a), PyArray_DIMS(a), "");
shape2 = convert_shape_to_string(PyArray_NDIM(b), PyArray_DIMS(b), "");
// 创建表示错误的维度索引的对象
i_obj = PyLong_FromLong(i);
j_obj = PyLong_FromLong(j);
// 获取导致错误的维度的大小并转换为 Python 对象
shape1_i = PyLong_FromSsize_t(PyArray_DIM(a, i));
shape2_j = PyLong_FromSsize_t(PyArray_DIM(b, j));
// 如果创建任何对象失败,则跳转到 end 标签
if (!format || !shape1 || !shape2 || !i_obj || !j_obj ||
!shape1_i || !shape2_j) {
goto end;
}
// 打包所有参数到元组中
fmt_args = PyTuple_Pack(6, shape1, shape2,
shape1_i, i_obj, shape2_j, j_obj);
if (fmt_args == NULL) {
goto end;
}
// 格式化错误消息
errmsg = PyUnicode_Format(format, fmt_args);
if (errmsg != NULL) {
// 设置值错误异常并附上错误消息
PyErr_SetObject(PyExc_ValueError, errmsg);
}
else {
// 如果无法格式化消息,设置通用错误消息
PyErr_SetString(PyExc_ValueError, "shapes are not aligned");
}
end:
// 释放所有创建的 Python 对象
Py_XDECREF(errmsg);
Py_XDECREF(fmt_args);
Py_XDECREF(format);
Py_XDECREF(i_obj);
Py_XDECREF(j_obj);
Py_XDECREF(shape1);
Py_XDECREF(shape2);
Py_XDECREF(shape1_i);
Py_XDECREF(shape2_j);
}
/**
* _unpack_field - 解包 PyDataType_FIELDS(dtype) 元组
*
* @param value: 应为元组
* @param descr: 将被设置为字段的数据类型描述符
* @param offset: 将被设置为字段的偏移量
*
* @return: 失败返回 -1,成功返回 0
*/
NPY_NO_EXPORT int
_unpack_field(PyObject *value, PyArray_Descr **descr, npy_intp *offset)
{
PyObject *off;
// 检查元组长度,如果小于 2,返回错误
if (PyTuple_GET_SIZE(value) < 2) {
return -1;
}
// 设置描述符为元组的第一个元素
*descr = (PyArray_Descr *)PyTuple_GET_ITEM(value, 0);
// 设置偏移量为元组的第二个元素
off = PyTuple_GET_ITEM(value, 1);
// 如果偏移量是长整型,转换为 ssize_t 类型
if (PyLong_Check(off)) {
*offset = PyLong_AsSsize_t(off);
}
else {
// 如果无法转换,设置索引错误异常
PyErr_SetString(PyExc_IndexError, "can't convert offset");
return -1;
}
return 0;
}
/**
* _may_have_objects - 检查数据类型是否可能包含对象字段
*
* @param dtype: 要检查的数组数据类型描述符
*
* @return: 如果数据类型可能包含对象字段返回非零值,否则返回 0
*/
NPY_NO_EXPORT int
_may_have_objects(PyArray_Descr *dtype)
{
PyArray_Descr *base = dtype;
// 如果数据类型是子数组,则获取基础数据类型
if (PyDataType_HASSUBARRAY(dtype)) {
base = ((_PyArray_LegacyDescr *)dtype)->subarray->base;
}
// 检查数据类型是否有字段或者是否标记为可能包含对象
return (PyDataType_HASFIELDS(base) ||
PyDataType_FLAGCHK(base, NPY_ITEM_HASOBJECT));
}
/*
* 创建一个新的空数组,尺寸为传入的大小,考虑到ap1和ap2的优先级。
*
* 如果`out`非空,则检查与ap1和ap2的内存重叠情况,并可能返回一个updateifcopy临时数组。
* 如果`result`非空,则递增引用并将要返回的输出数组(如果`out`非空,则为`out`;否则为新分配的数组)放入*result。
*/
NPY_NO_EXPORT PyArrayObject *
new_array_for_sum(PyArrayObject *ap1, PyArrayObject *ap2, PyArrayObject* out,
int nd, npy_intp dimensions[], int typenum, PyArrayObject **result)
{
PyArrayObject *out_buf; // 声明PyArrayObject类型的指针out_buf
if (out) {
int d;
/* 验证out是否可用 */
if (PyArray_NDIM(out) != nd ||
PyArray_TYPE(out) != typenum ||
!PyArray_ISCARRAY(out)) {
PyErr_SetString(PyExc_ValueError,
"output array is not acceptable (must have the right datatype, "
"number of dimensions, and be a C-Array)");
return 0; // 返回0,表示出错
}
for (d = 0; d < nd; ++d) {
if (dimensions[d] != PyArray_DIM(out, d)) {
PyErr_SetString(PyExc_ValueError,
"output array has wrong dimensions");
return 0; // 返回0,表示出错
}
}
/* 检查内存重叠 */
if (!(solve_may_share_memory(out, ap1, 1) == 0 &&
solve_may_share_memory(out, ap2, 1) == 0)) {
/* 分配临时输出数组 */
out_buf = (PyArrayObject *)PyArray_NewLikeArray(out, NPY_CORDER,
NULL, 0);
if (out_buf == NULL) {
return NULL; // 返回NULL,表示出错
}
/* 设置写回复制 */
Py_INCREF(out); // 递增引用计数
if (PyArray_SetWritebackIfCopyBase(out_buf, out) < 0) {
Py_DECREF(out);
Py_DECREF(out_buf);
return NULL; // 返回NULL,表示出错
}
}
else {
Py_INCREF(out); // 递增引用计数
out_buf = out;
}
if (result) {
Py_INCREF(out); // 递增引用计数
*result = out; // 将out赋值给result指向的变量
}
return out_buf; // 返回out_buf指向的数组对象
}
else {
PyTypeObject *subtype;
double prior1, prior2;
/*
* Need to choose an output array that can hold a sum
* -- use priority to determine which subtype.
*/
// 如果两个数组的类型不同,需要根据优先级选择一个能容纳和的输出数组类型
if (Py_TYPE(ap2) != Py_TYPE(ap1)) {
// 获取第二个数组的优先级
prior2 = PyArray_GetPriority((PyObject *)ap2, 0.0);
// 获取第一个数组的优先级
prior1 = PyArray_GetPriority((PyObject *)ap1, 0.0);
// 根据优先级选择子类型
subtype = (prior2 > prior1 ? Py_TYPE(ap2) : Py_TYPE(ap1));
}
else {
// 如果数组类型相同,则优先级相同,选择第一个数组的类型作为子类型
prior1 = prior2 = 0.0;
subtype = Py_TYPE(ap1);
}
// 创建新的数组对象作为输出缓冲区,使用选择的子类型
out_buf = (PyArrayObject *)PyArray_New(subtype, nd, dimensions,
typenum, NULL, NULL, 0, 0,
(PyObject *)
(prior2 > prior1 ? ap2 : ap1));
// 如果成功创建输出缓冲区,并且结果指针有效,则增加输出缓冲区的引用计数,并设置结果指针
if (out_buf != NULL && result) {
Py_INCREF(out_buf);
*result = out_buf;
}
// 返回输出缓冲区对象
return out_buf;
}
```cpp`
/* 检查一个 NumPy 数组是否可以转换为标量 */
NPY_NO_EXPORT int
check_is_convertible_to_scalar(PyArrayObject *v)
{
// 如果数组的维度为 0,说明是标量,可以直接转换
if (PyArray_NDIM(v) == 0) {
return 0;
}
/* 移除此 if-else 块当不再需要支持该功能时 */
// 如果数组大小为 1,即只有一个元素
if (PyArray_SIZE(v) == 1) {
/* Numpy 1.25.0, 2023-01-02 */
// 发出弃用警告并返回 -1,表示转换不再支持
if (DEPRECATE(
"Conversion of an array with ndim > 0 to a scalar "
"is deprecated, and will error in future. "
"Ensure you extract a single element from your array "
"before performing this operation. "
"(Deprecated NumPy 1.25.)") < 0) {
return -1;
}
return 0;
} else {
// 如果数组大小不为 1,则抛出类型错误异常
PyErr_SetString(PyExc_TypeError,
"only length-1 arrays can be converted to Python scalars");
return -1;
}
// 由于之前的 return 语句会中断函数执行,因此这部分代码不会被执行到
// 抛出类型错误异常,指示只有 0 维数组才能转换为 Python 标量
PyErr_SetString(PyExc_TypeError,
"only 0-dimensional arrays can be converted to Python scalars");
return -1;
}
.\numpy\numpy\_core\src\multiarray\common.h
do { \
if (!NpyIter_IterationNeedsAPI(iter)) { \ // 检查迭代器是否需要 API
NPY_BEGIN_THREADS_THRESHOLDED(NpyIter_GetIterSize(iter)); \ // 根据迭代器大小设置线程阈值
} \
} while(0)
NPY_NO_EXPORT PyArray_Descr * // 声明不导出的函数返回 PyArray_Descr 指针
PyArray_DTypeFromObjectStringDiscovery(
PyObject *obj, PyArray_Descr *last_dtype, int string_type); // 函数原型,从对象字符串中发现数据类型
/*
* Recursively examines the object to determine an appropriate dtype
* to use for converting to an ndarray.
*
* 'obj' is the object to be converted to an ndarray.
*
* 'maxdims' is the maximum recursion depth.
*
* 'out_dtype' should be either NULL or a minimal starting dtype when
* the function is called. It is updated with the results of type
* promotion. This dtype does not get updated when processing NA objects.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int // 声明不导出的函数返回整型
PyArray_DTypeFromObject(PyObject *obj, int maxdims,
PyArray_Descr **out_dtype); // 函数原型,从对象中确定适当的数据类型
/*
* Returns NULL without setting an exception if no scalar is matched, a
* new dtype reference otherwise.
*/
NPY_NO_EXPORT PyArray_Descr * // 声明不导出的函数返回 PyArray_Descr 指针
_array_find_python_scalar_type(PyObject *op); // 函数原型,查找 Python 标量类型
NPY_NO_EXPORT npy_bool // 声明不导出的函数返回 npy_bool 类型
_IsWriteable(PyArrayObject *ap); // 函数原型,检查数组是否可写
NPY_NO_EXPORT PyObject * // 声明不导出的函数返回 PyObject 指针
convert_shape_to_string(npy_intp n, npy_intp const *vals, char *ending); // 函数原型,将形状转换为字符串
/*
* Sets ValueError with "matrices not aligned" message for np.dot and friends
* when a.shape[i] should match b.shape[j], but doesn't.
*/
NPY_NO_EXPORT void // 声明不导出的函数返回空
dot_alignment_error(PyArrayObject *a, int i, PyArrayObject *b, int j); // 函数原型,设置 "matrices not aligned" 错误消息
/**
* unpack tuple of PyDataType_FIELDS(dtype) (descr, offset, title[not-needed])
*
* @param "value" should be the tuple.
*
* @return "descr" will be set to the field's dtype
* @return "offset" will be set to the field's offset
*
* returns -1 on failure, 0 on success.
*/
NPY_NO_EXPORT int // 声明不导出的函数返回整型
_unpack_field(PyObject *value, PyArray_Descr **descr, npy_intp *offset); // 函数原型,解压 PyDataType_FIELDS 元组
/*
* check whether arrays with datatype dtype might have object fields. This will
* only happen for structured dtypes (which may have hidden objects even if the
* HASOBJECT flag is false), object dtypes, or subarray dtypes whose base type
* is either of these.
*/
NPY_NO_EXPORT int // 声明不导出的函数返回整型
_may_have_objects(PyArray_Descr *dtype); // 函数原型,检查数据类型是否可能包含对象字段
#endif // NUMPY_CORE_SRC_MULTIARRAY_COMMON_H_
/*
* Returns -1 and sets an exception if *index is an invalid index for
* an array of size max_item, otherwise adjusts it in place to be
* 0 <= *index < max_item, and returns 0.
* 'axis' should be the array axis that is being indexed over, if known. If
* unknown, use -1.
* If _save is NULL it is assumed the GIL is taken
* If _save is not NULL it is assumed the GIL is not taken and it
* is acquired in the case of an error
*/
static inline int
check_and_adjust_index(npy_intp *index, npy_intp max_item, int axis,
PyThreadState * _save)
{
/* Check that index is valid, taking into account negative indices */
if (NPY_UNLIKELY((*index < -max_item) || (*index >= max_item))) {
NPY_END_THREADS; // Release the GIL before raising an exception
/* Try to be as clear as possible about what went wrong. */
if (axis >= 0) {
PyErr_Format(PyExc_IndexError,
"index %"NPY_INTP_FMT" is out of bounds "
"for axis %d with size %"NPY_INTP_FMT,
*index, axis, max_item); // Format error message for axis index
} else {
PyErr_Format(PyExc_IndexError,
"index %"NPY_INTP_FMT" is out of bounds "
"for size %"NPY_INTP_FMT, *index, max_item); // Format error message for size index
}
return -1; // Return -1 indicating error
}
/* adjust negative indices */
if (*index < 0) {
*index += max_item; // Adjust negative index to positive
}
return 0; // Return 0 indicating success
}
/*
* Returns -1 and sets an exception if *axis is an invalid axis for
* an array of dimension ndim, otherwise adjusts it in place to be
* 0 <= *axis < ndim, and returns 0.
*
* msg_prefix: borrowed reference, a string to prepend to the message
*/
static inline int
check_and_adjust_axis_msg(int *axis, int ndim, PyObject *msg_prefix)
{
/* Check that axis is valid, taking into account negative indices */
if (NPY_UNLIKELY((*axis < -ndim) || (*axis >= ndim))) {
/* Invoke the AxisError constructor */
PyObject *exc = PyObject_CallFunction(
npy_static_pydata.AxisError, "iiO", *axis, ndim,
msg_prefix); // Create AxisError exception with axis information
if (exc == NULL) {
return -1; // Return -1 indicating error
}
PyErr_SetObject(npy_static_pydata.AxisError, exc); // Set the created exception object
Py_DECREF(exc); // Decrement reference count of the exception object
return -1; // Return -1 indicating error
}
/* adjust negative indices */
if (*axis < 0) {
*axis += ndim; // Adjust negative axis index to positive
}
return 0; // Return 0 indicating success
}
static inline int
check_and_adjust_axis(int *axis, int ndim)
{
return check_and_adjust_axis_msg(axis, ndim, Py_None); // Call check_and_adjust_axis_msg with default message prefix
}
/* used for some alignment checks */
/*
* GCC releases before GCC 4.9 had a bug in _Alignof. See GCC bug 52023
* <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52023>.
* clang versions < 8.0.0 have the same bug.
*/
#if (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112 \
|| (defined __GNUC__ && __GNUC__ < 4 + (__GNUC_MINOR__ < 9) \
&& !defined __clang__) \
|| (defined __clang__ && __clang_major__ < 8))
# define NPY_ALIGNOF(type) offsetof(struct {char c; type v;}, v) // Macro definition for alignment check
#else
# define NPY_ALIGNOF(type) _Alignof(type) // Macro definition for alignment check
#endif
#endif
#define NPY_ALIGNOF_UINT(type) npy_uint_alignment(sizeof(type))
/*
* 禁用无害的编译器警告 "4116: unnamed type definition in
* parentheses",这是由 _ALIGN 宏引起的。
*/
#if defined(_MSC_VER)
#pragma warning(disable:4116)
#endif
/*
* 如果指针对齐到 'alignment',则返回 true
*/
static inline int
npy_is_aligned(const void * p, const npy_uintp alignment)
{
/*
* 假设 alignment 是 2 的幂,符合 C 标准要求。
* 假设从指针转换为 uintp 后可以进行比特位 & 运算(不是 C 标准要求,但 glibc 中使用)。
* 这个测试比直接取模更快。
* 注意 alignment 值为 0 是允许的,并返回 False。
*/
return ((npy_uintp)(p) & ((alignment) - 1)) == 0;
}
/* 获取相应的 "uint" 对齐,根据 itemsize,在复制代码中使用 */
static inline npy_uintp
npy_uint_alignment(int itemsize)
{
npy_uintp alignment = 0; /* 返回值为 0 表示不对齐 */
switch(itemsize){
case 1:
return 1;
case 2:
alignment = NPY_ALIGNOF(npy_uint16);
break;
case 4:
alignment = NPY_ALIGNOF(npy_uint32);
break;
case 8:
alignment = NPY_ALIGNOF(npy_uint64);
break;
case 16:
/*
* 16 字节类型使用 2 个 uint64 赋值进行复制。
* 参见 lowlevel_strided_loops.c 中的跨步复制函数。
*/
alignment = NPY_ALIGNOF(npy_uint64);
break;
default:
break;
}
return alignment;
}
/*
* 带有步长和反转参数的 memchr
* 适用于小搜索,其中调用 libc 的 memchr 代价高昂。
* 步长必须是大小的倍数。
* 与 memchr 不同,如果未找到 needle,则返回末尾的一个步长。
*/
#ifdef __clang__
/*
* 下面的代码当前使用 !NPY_ALIGNMENT_REQUIRED,这应该是可以的,
* 但会导致 clang sanitizer 发出警告。可以修改代码以避免这种“非对齐”访问,
* 但应仔细检查性能变化。
*/
__attribute__((no_sanitize("alignment")))
#endif
static inline char *
npy_memchr(char * haystack, char needle,
npy_intp stride, npy_intp size, npy_intp * psubloopsize, int invert)
{
char * p = haystack;
npy_intp subloopsize = 0;
if (!invert) {
/*
* 这通常是确定要处理的元素的路径,
* 这里性能不是很重要。
* 如果 0 字节靠近开始,memchr 的设置成本很高。
*/
while (subloopsize < size && *p != needle) {
subloopsize++;
p += stride;
}
}
else {
/* 否则情况下,处理通常是跳过路径元素 */
if (!NPY_ALIGNMENT_REQUIRED && needle == 0 && stride == 1) {
/* 如果不需要对齐且查找元素为0且步长为1时 */
/* 迭代直到最后一个4的倍数 */
char * block_end = haystack + size - (size % sizeof(unsigned int));
while (p < block_end) {
unsigned int v = *(unsigned int*)p;
if (v != 0) {
break;
}
p += sizeof(unsigned int);
}
/* 处理剩余部分 */
subloopsize = (p - haystack);
}
while (subloopsize < size && *p == needle) {
subloopsize++;
p += stride;
}
}
*psubloopsize = subloopsize;
return p;
/*
* Simple helper to create a tuple from an array of items. The `make_null_none`
* flag means that NULL entries are replaced with None, which is occasionally
* useful.
*/
static inline PyObject *
PyArray_TupleFromItems(int n, PyObject *const *items, int make_null_none)
{
// 创建一个包含 n 个元素的元组
PyObject *tuple = PyTuple_New(n);
if (tuple == NULL) {
return NULL;
}
// 遍历 items 数组,将每个元素添加到 tuple 中
for (int i = 0; i < n; i ++) {
PyObject *tmp;
// 如果 make_null_none 为真且 items[i] 为 NULL,则使用 Py_None 替代
if (!make_null_none || items[i] != NULL) {
tmp = items[i];
}
else {
tmp = Py_None;
}
// 增加 tmp 的引用计数,并将其设置为 tuple 的第 i 个元素
Py_INCREF(tmp);
PyTuple_SET_ITEM(tuple, i, tmp);
}
return tuple;
}
/*
* Returns 0 if the array has rank 0, -1 otherwise. Prints a deprecation
* warning for arrays of _size_ 1.
*/
NPY_NO_EXPORT int
check_is_convertible_to_scalar(PyArrayObject *v);
#include "ucsnarrow.h"
/*
* Make a new empty array, of the passed size, of a type that takes the
* priority of ap1 and ap2 into account.
*
* If `out` is non-NULL, memory overlap is checked with ap1 and ap2, and an
* updateifcopy temporary array may be returned. If `result` is non-NULL, the
* output array to be returned (`out` if non-NULL and the newly allocated array
* otherwise) is incref'd and put to *result.
*/
NPY_NO_EXPORT PyArrayObject *
new_array_for_sum(PyArrayObject *ap1, PyArrayObject *ap2, PyArrayObject* out,
int nd, npy_intp dimensions[], int typenum, PyArrayObject **result);
/*
* Used to indicate a broadcast axis, see also `npyiter_get_op_axis` in
* `nditer_constr.c`. This may be the preferred API for reduction axes
* probably. So we should consider making this public either as a macro or
* function (so that the way we flag the axis can be changed).
*/