NumPy 源码解析(五十八)
.\numpy\numpy\_core\src\multiarray\abstractdtypes.h
extern "C" {
/*
* These are mainly needed for value based promotion in ufuncs. It
* may be necessary to make them (partially) public, to allow user-defined
* dtypes to perform value based casting.
*/
// 定义一些用于 ufunc 中数值提升的抽象数据类型元信息,可能需要部分公开以允许用户定义的数据类型执行值基础的类型转换
NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_IntAbstractDType;
NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_FloatAbstractDType;
NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_ComplexAbstractDType;
NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyLongDType;
NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyFloatDType;
NPY_NO_EXPORT extern PyArray_DTypeMeta PyArray_PyComplexDType;
// 初始化并映射 Python 类型到数据类型的函数声明
NPY_NO_EXPORT int
initialize_and_map_pytypes_to_dtypes(void);
/*
* When we get a Python int, float, or complex, we may have to use weak
* promotion logic.
* To implement this, we sometimes have to tag the converted (temporary)
* array when the original object was a Python scalar.
*
* @param obj The original Python object.
* @param arr The array into which the Python object was converted.
* @param[in,out] **dtype A pointer to the array's DType, if not NULL it will be
* replaced with the abstract DType.
* @return 0 if the `obj` was not a python scalar, and 1 if it was.
*/
// 如果输入的 Python 对象是标量,根据其类型进行弱类型提升处理,标记临时数组和更新数据类型信息
static inline int
npy_mark_tmp_array_if_pyscalar(
PyObject *obj, PyArrayObject *arr, PyArray_DTypeMeta **dtype)
{
/*
* We check the array dtype for two reasons: First, booleans are
* integer subclasses. Second, an int, float, or complex could have
* a custom DType registered, and then we should use that.
* Further, `np.float64` is a double subclass, so must reject it.
*/
// 如果原始对象是 Python 的整数并且数组是整数类型或对象类型,标记数组并更新数据类型为 Python 整数类型
if (PyLong_Check(obj)
&& (PyArray_ISINTEGER(arr) || PyArray_ISOBJECT(arr))) {
((PyArrayObject_fields *)arr)->flags |= NPY_ARRAY_WAS_PYTHON_INT;
if (dtype != NULL) {
Py_INCREF(&PyArray_PyLongDType);
Py_SETREF(*dtype, &PyArray_PyLongDType);
}
return 1;
}
// 如果原始对象是 Python 的浮点数并且数组不是双精度浮点数类型,标记数组并更新数据类型为 Python 浮点数类型
else if (PyFloat_Check(obj) && !PyArray_IsScalar(obj, Double)
&& PyArray_TYPE(arr) == NPY_DOUBLE) {
((PyArrayObject_fields *)arr)->flags |= NPY_ARRAY_WAS_PYTHON_FLOAT;
if (dtype != NULL) {
Py_INCREF(&PyArray_PyFloatDType);
Py_SETREF(*dtype, &PyArray_PyFloatDType);
}
return 1;
}
// 如果原始对象是 Python 的复数并且数组不是双精度复数类型,标记数组并更新数据类型为 Python 复数类型
else if (PyComplex_Check(obj) && !PyArray_IsScalar(obj, CDouble)
&& PyArray_TYPE(arr) == NPY_CDOUBLE) {
((PyArrayObject_fields *)arr)->flags |= NPY_ARRAY_WAS_PYTHON_COMPLEX;
if (dtype != NULL) {
Py_INCREF(&PyArray_PyComplexDType);
Py_SETREF(*dtype, &PyArray_PyComplexDType);
}
return 1;
}
// 如果输入对象不是 Python 标量类型,返回 0
return 0;
}
#ifdef __cplusplus
}
#endif
#endif /* NUMPY_CORE_SRC_MULTIARRAY_ABSTRACTDTYPES_H_ */
.\numpy\numpy\_core\src\multiarray\alloc.c
/*
* 定义 NPY_NO_DEPRECATED_API 以及 _MULTIARRAYMODULE,这些是预处理器宏
* NPY_SSIZE_T_CLEAN 用于确保 Py_ssize_t 被定义为 size_t,这是一种约定
* 包含 Python.h,structmember.h,pymem.h 以及一系列 NumPy 头文件
* assert.h 被包含用于运行时断言
* 对于 Linux 系统,包含 sys/mman.h 以及在内核版本低于 2.6.38 时定义 MADV_HUGEPAGE
*/
/*
* 定义 NBUCKETS、NBUCKETS_DIM 和 NCACHE 分别作为数据、维度/步长的缓存桶数目以及每个缓存桶中的缓存条目数
* 定义 cache_bucket 结构,用于缓存指针
* datacache 和 dimcache 是全局静态变量,用于存储数据和维度的缓存
*/
typedef struct {
npy_uintp available; /* number of cached pointers */
void * ptrs[NCACHE];
} cache_bucket;
static cache_bucket datacache[NBUCKETS];
static cache_bucket dimcache[NBUCKETS_DIM];
/*
* 下面的函数定义了两个功能:
* - _get_madvise_hugepage 检查是否启用了 MADV_HUGEPAGE,返回一个 Python 布尔值
* - _set_madvise_hugepage 启用或禁用 MADV_HUGEPAGE,并返回先前的设置
* 这两个函数都是 NPY_NO_EXPORT,意味着它们在库外不可见
*/
NPY_NO_EXPORT PyObject *
_get_madvise_hugepage(PyObject *NPY_UNUSED(self), PyObject *NPY_UNUSED(args))
{
if (npy_thread_unsafe_state.madvise_hugepage) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
NPY_NO_EXPORT PyObject *
_set_madvise_hugepage(PyObject *NPY_UNUSED(self), PyObject *enabled_obj)
{
int was_enabled = npy_thread_unsafe_state.madvise_hugepage;
int enabled = PyObject_IsTrue(enabled_obj);
if (enabled < 0) {
return NULL;
}
npy_thread_unsafe_state.madvise_hugepage = enabled;
if (was_enabled) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
/*
* _npy_alloc_cache 是一个内联函数,用于管理小内存块缓存,避免使用更昂贵的 libc 分配
* 根据 nelem、esz 和 msz 的值,决定从 cache 中获取缓存指针还是进行新分配
* 要求在调用此函数时必须持有全局解释器锁 (GIL)
*/
static inline void *
_npy_alloc_cache(npy_uintp nelem, npy_uintp esz, npy_uint msz,
cache_bucket * cache, void * (*alloc)(size_t))
{
void * p;
assert((esz == 1 && cache == datacache) ||
(esz == sizeof(npy_intp) && cache == dimcache));
assert(PyGILState_Check());
if (nelem < msz) {
if (cache[nelem].available > 0) {
return cache[nelem].ptrs[--(cache[nelem].available)];
}
}
p = alloc(nelem * esz);
if (p) {
_PyPyPyGC_AddMemoryPressure(nelem * esz);
/* 允许内核为大数组分配巨大页面 */
if (NPY_UNLIKELY(nelem * esz >= ((1u<<22u))) &&
npy_thread_unsafe_state.madvise_hugepage) {
npy_uintp offset = 4096u - (npy_uintp)p % (4096u);
npy_uintp length = nelem * esz - offset;
/**
* 故意不检查可能由旧内核版本返回的错误;乐观地尝试启用巨大页面。
*/
madvise((void*)((npy_uintp)p + offset), length, MADV_HUGEPAGE);
}
}
return p;
}
/*
* 返回指针 p 到缓存,nelem 是缓存桶大小的元素数(1 或 sizeof(npy_intp))
*/
static inline void
_npy_free_cache(void * p, npy_uintp nelem, npy_uint msz,
cache_bucket * cache, void (*dealloc)(void *))
{
assert(PyGILState_Check());
if (p != NULL && nelem < msz) {
if (cache[nelem].available < NCACHE) {
cache[nelem].ptrs[cache[nelem].available++] = p;
return;
}
}
dealloc(p);
}
/*
* 数组数据缓存,sz 是要分配的字节数
*/
NPY_NO_EXPORT void *
npy_alloc_cache(npy_uintp sz)
{
return _npy_alloc_cache(sz, 1, NBUCKETS, datacache, &PyDataMem_NEW);
}
/* 零初始化数据,sz 是要分配的字节数 */
NPY_NO_EXPORT void *
npy_alloc_cache_zero(size_t nmemb, size_t size)
{
void * p;
size_t sz = nmemb * size;
NPY_BEGIN_THREADS_DEF;
if (sz < NBUCKETS) {
p = _npy_alloc_cache(sz, 1, NBUCKETS, datacache, &PyDataMem_NEW);
if (p) {
memset(p, 0, sz);
}
return p;
}
NPY_BEGIN_THREADS;
p = PyDataMem_NEW_ZEROED(nmemb, size);
NPY_END_THREADS;
return p;
}
NPY_NO_EXPORT void
npy_free_cache(void * p, npy_uintp sz)
{
_npy_free_cache(p, sz, NBUCKETS, datacache, &PyDataMem_FREE);
}
/*
* 维度/步幅缓存,使用不同的分配器,并始终是 npy_intp 的倍数
*/
NPY_NO_EXPORT void *
npy_alloc_cache_dim(npy_uintp sz)
{
/*
* 确保任何临时分配可以用于数组元数据,该元数据使用一个内存块存储维度和步幅
*/
if (sz < 2) {
sz = 2;
}
return _npy_alloc_cache(sz, sizeof(npy_intp), NBUCKETS_DIM, dimcache,
&PyArray_malloc);
}
NPY_NO_EXPORT void
npy_free_cache_dim(void * p, npy_uintp sz)
{
/* 见 npy_alloc_cache_dim */
if (sz < 2) {
sz = 2;
}
_npy_free_cache(p, sz, NBUCKETS_DIM, dimcache,
&PyArray_free);
}
/* 类似于 arrayobject.c 中的 array_dealloc */
static inline void
WARN_NO_RETURN(PyObject* warning, const char * msg) {
if (PyErr_WarnEx(warning, msg, 1) < 0) {
PyObject * s;
s = PyUnicode_FromString("PyDataMem_UserFREE");
if (s) {
PyErr_WriteUnraisable(s);
Py_DECREF(s);
}
else {
PyErr_WriteUnraisable(Py_None);
}
}
/*NUMPY_API
* Allocates memory for array data.
*/
NPY_NO_EXPORT void *
PyDataMem_NEW(size_t size)
{
void *result;
assert(size != 0); // 确保分配的大小不为零
result = malloc(size); // 调用标准库函数分配内存
PyTraceMalloc_Track(NPY_TRACE_DOMAIN, (npy_uintp)result, size); // 调用跟踪内存分配的函数
return result; // 返回分配的内存地址
}
/*NUMPY_API
* Allocates zeroed memory for array data.
*/
NPY_NO_EXPORT void *
PyDataMem_NEW_ZEROED(size_t nmemb, size_t size)
{
void *result;
result = calloc(nmemb, size); // 调用标准库函数分配并清零内存
PyTraceMalloc_Track(NPY_TRACE_DOMAIN, (npy_uintp)result, nmemb * size); // 调用跟踪内存分配的函数
return result; // 返回分配的内存地址
}
/*NUMPY_API
* Free memory for array data.
*/
NPY_NO_EXPORT void
PyDataMem_FREE(void *ptr)
{
PyTraceMalloc_Untrack(NPY_TRACE_DOMAIN, (npy_uintp)ptr); // 调用取消跟踪内存分配的函数
free(ptr); // 释放内存
}
/*NUMPY_API
* Reallocate/resize memory for array data.
*/
NPY_NO_EXPORT void *
PyDataMem_RENEW(void *ptr, size_t size)
{
void *result;
assert(size != 0); // 确保重新分配的大小不为零
result = realloc(ptr, size); // 调用标准库函数重新分配内存
if (result != ptr) {
PyTraceMalloc_Untrack(NPY_TRACE_DOMAIN, (npy_uintp)ptr); // 如果地址有变化,则取消旧地址的内存跟踪
}
PyTraceMalloc_Track(NPY_TRACE_DOMAIN, (npy_uintp)result, size); // 调用跟踪内存分配的函数
return result; // 返回重新分配的内存地址
}
// The default data mem allocator malloc routine does not make use of a ctx.
// It should be called only through PyDataMem_UserNEW
// since itself does not handle eventhook and tracemalloc logic.
static inline void *
default_malloc(void *NPY_UNUSED(ctx), size_t size)
{
return _npy_alloc_cache(size, 1, NBUCKETS, datacache, &malloc); // 调用特定分配缓存的函数
}
// The default data mem allocator calloc routine does not make use of a ctx.
// It should be called only through PyDataMem_UserNEW_ZEROED
// since itself does not handle eventhook and tracemalloc logic.
static inline void *
default_calloc(void *NPY_UNUSED(ctx), size_t nelem, size_t elsize)
{
void * p;
size_t sz = nelem * elsize;
NPY_BEGIN_THREADS_DEF; // 开始线程安全区域定义
if (sz < NBUCKETS) {
p = _npy_alloc_cache(sz, 1, NBUCKETS, datacache, &malloc); // 调用特定分配缓存的函数
if (p) {
memset(p, 0, sz); // 如果分配成功,清零分配的内存
}
return p; // 返回分配的内存地址
}
NPY_BEGIN_THREADS; // 开始线程安全区域
p = calloc(nelem, elsize); // 调用标准库函数分配并清零内存
NPY_END_THREADS; // 结束线程安全区域
return p; // 返回分配的内存地址
}
// The default data mem allocator realloc routine does not make use of a ctx.
// It should be called only through PyDataMem_UserRENEW
// since itself does not handle eventhook and tracemalloc logic.
static inline void *
default_realloc(void *NPY_UNUSED(ctx), void *ptr, size_t new_size)
{
return realloc(ptr, new_size); // 调用标准库函数重新分配内存
}
// The default data mem allocator free routine does not make use of a ctx.
// It should be called only through PyDataMem_UserFREE
// since itself does not handle eventhook and tracemalloc logic.
static inline void
default_free(void *NPY_UNUSED(ctx), void *ptr, size_t size)
{
_npy_free_cache(ptr, size, NBUCKETS, datacache, &free); // 调用特定释放缓存的函数
}
/* Memory handler global default */
PyDataMem_Handler default_handler = {
"default_allocator", // 默认内存分配器的名称
1, // 是否线程安全的标志
{
NULL, /* 上下文为空 */
default_malloc, /* 分配内存的默认函数指针 */
default_calloc, /* 分配并清零内存的默认函数指针 */
default_realloc, /* 重新分配内存的默认函数指针 */
default_free /* 释放内存的默认函数指针 */
}
};
/* singleton capsule of the default handler */
PyObject *PyDataMem_DefaultHandler;
PyObject *current_handler;
int uo_index=0; /* user_override index */
/* Wrappers for the default or any user-assigned PyDataMem_Handler */
/* Allocate memory using the user-defined memory handler */
NPY_NO_EXPORT void *
PyDataMem_UserNEW(size_t size, PyObject *mem_handler)
{
void *result;
// Cast the PyObject mem_handler to PyDataMem_Handler*
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(
mem_handler, MEM_HANDLER_CAPSULE_NAME);
if (handler == NULL) {
return NULL; // Return NULL if mem_handler is invalid
}
assert(size != 0); // Ensure size is non-zero
// Call malloc function of the allocator stored in handler
result = handler->allocator.malloc(handler->allocator.ctx, size);
// Track memory allocation using PyTraceMalloc_Track
PyTraceMalloc_Track(NPY_TRACE_DOMAIN, (npy_uintp)result, size);
return result; // Return allocated memory pointer
}
/* Allocate zeroed memory using the user-defined memory handler */
NPY_NO_EXPORT void *
PyDataMem_UserNEW_ZEROED(size_t nmemb, size_t size, PyObject *mem_handler)
{
void *result;
// Cast the PyObject mem_handler to PyDataMem_Handler*
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(
mem_handler, MEM_HANDLER_CAPSULE_NAME);
if (handler == NULL) {
return NULL; // Return NULL if mem_handler is invalid
}
// Call calloc function of the allocator stored in handler
result = handler->allocator.calloc(handler->allocator.ctx, nmemb, size);
// Track memory allocation using PyTraceMalloc_Track
PyTraceMalloc_Track(NPY_TRACE_DOMAIN, (npy_uintp)result, nmemb * size);
return result; // Return allocated memory pointer
}
/* Free memory using the user-defined memory handler */
NPY_NO_EXPORT void
PyDataMem_UserFREE(void *ptr, size_t size, PyObject *mem_handler)
{
// Cast the PyObject mem_handler to PyDataMem_Handler*
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(
mem_handler, MEM_HANDLER_CAPSULE_NAME);
if (handler == NULL) {
// Issue a warning and return if mem_handler is invalid
WARN_NO_RETURN(PyExc_RuntimeWarning,
"Could not get pointer to 'mem_handler' from PyCapsule");
return;
}
// Untrack memory allocation using PyTraceMalloc_Untrack
PyTraceMalloc_Untrack(NPY_TRACE_DOMAIN, (npy_uintp)ptr);
// Call free function of the allocator stored in handler
handler->allocator.free(handler->allocator.ctx, ptr, size);
}
/* Reallocate memory using the user-defined memory handler */
NPY_NO_EXPORT void *
PyDataMem_UserRENEW(void *ptr, size_t size, PyObject *mem_handler)
{
void *result;
// Cast the PyObject mem_handler to PyDataMem_Handler*
PyDataMem_Handler *handler = (PyDataMem_Handler *) PyCapsule_GetPointer(
mem_handler, MEM_HANDLER_CAPSULE_NAME);
if (handler == NULL) {
return NULL; // Return NULL if mem_handler is invalid
}
assert(size != 0); // Ensure size is non-zero
// Call realloc function of the allocator stored in handler
result = handler->allocator.realloc(handler->allocator.ctx, ptr, size);
// If reallocation changes the pointer, untrack old and track new
if (result != ptr) {
PyTraceMalloc_Untrack(NPY_TRACE_DOMAIN, (npy_uintp)ptr);
}
PyTraceMalloc_Track(NPY_TRACE_DOMAIN, (npy_uintp)result, size);
return result; // Return reallocated memory pointer
}
/*NUMPY_API
* Set a new allocation policy. If the input value is NULL, will reset
* the policy to the default. Return the previous policy, or
* return NULL if an error has occurred. We wrap the user-provided
* functions so they will still call the python and numpy
* memory management callback hooks.
*/
NPY_NO_EXPORT PyObject *
PyDataMem_SetHandler(PyObject *handler)
{
PyObject *old_handler;
PyObject *token;
// Get the current handler from context, return NULL on failure
if (PyContextVar_Get(current_handler, NULL, &old_handler)) {
return NULL;
}
// If handler is NULL, set it to the default handler
if (handler == NULL) {
handler = PyDataMem_DefaultHandler;
}
if (!PyCapsule_IsValid(handler, MEM_HANDLER_CAPSULE_NAME)) {
PyErr_SetString(PyExc_ValueError, "Capsule must be named 'mem_handler'");
return NULL;
}
token = PyContextVar_Set(current_handler, handler);
if (token == NULL) {
Py_DECREF(old_handler);
return NULL;
}
Py_DECREF(token);
return old_handler;
/*NUMPY_API
* 返回下一个 PyArrayObject 分配数据的策略。如果失败,则返回 NULL。
*/
NPY_NO_EXPORT PyObject *
PyDataMem_GetHandler()
{
PyObject *handler;
// 获取当前的内存处理器对象
if (PyContextVar_Get(current_handler, NULL, &handler)) {
return NULL;
}
return handler; // 返回内存处理器对象
}
NPY_NO_EXPORT PyObject *
get_handler_name(PyObject *NPY_UNUSED(self), PyObject *args)
{
PyObject *arr=NULL;
// 解析参数,获取可能传入的 ndarray 对象
if (!PyArg_ParseTuple(args, "|O:get_handler_name", &arr)) {
return NULL;
}
// 如果传入的不是 ndarray 对象,则抛出异常
if (arr != NULL && !PyArray_Check(arr)) {
PyErr_SetString(PyExc_ValueError, "if supplied, argument must be an ndarray");
return NULL;
}
PyObject *mem_handler;
PyDataMem_Handler *handler;
PyObject *name;
// 如果传入了 ndarray 对象
if (arr != NULL) {
// 获取 ndarray 的内存处理器对象
mem_handler = PyArray_HANDLER((PyArrayObject *) arr);
// 如果获取失败,返回 None
if (mem_handler == NULL) {
Py_RETURN_NONE;
}
Py_INCREF(mem_handler);
}
else {
// 否则,获取默认的内存处理器对象
mem_handler = PyDataMem_GetHandler();
// 如果获取失败,返回 NULL
if (mem_handler == NULL) {
return NULL;
}
}
// 从内存处理器对象中获取处理器结构体指针
handler = (PyDataMem_Handler *) PyCapsule_GetPointer(
mem_handler, MEM_HANDLER_CAPSULE_NAME);
// 如果获取失败,释放内存处理器对象并返回 NULL
if (handler == NULL) {
Py_DECREF(mem_handler);
return NULL;
}
// 从处理器结构体中获取处理器名称,转换成 Python 字符串对象
name = PyUnicode_FromString(handler->name);
Py_DECREF(mem_handler);
return name; // 返回处理器名称对象
}
NPY_NO_EXPORT PyObject *
get_handler_version(PyObject *NPY_UNUSED(self), PyObject *args)
{
PyObject *arr=NULL;
// 解析参数,获取可能传入的 ndarray 对象
if (!PyArg_ParseTuple(args, "|O:get_handler_version", &arr)) {
return NULL;
}
// 如果传入的不是 ndarray 对象,则抛出异常
if (arr != NULL && !PyArray_Check(arr)) {
PyErr_SetString(PyExc_ValueError, "if supplied, argument must be an ndarray");
return NULL;
}
PyObject *mem_handler;
PyDataMem_Handler *handler;
PyObject *version;
// 如果传入了 ndarray 对象
if (arr != NULL) {
// 获取 ndarray 的内存处理器对象
mem_handler = PyArray_HANDLER((PyArrayObject *) arr);
// 如果获取失败,返回 None
if (mem_handler == NULL) {
Py_RETURN_NONE;
}
Py_INCREF(mem_handler);
}
else {
// 否则,获取默认的内存处理器对象
mem_handler = PyDataMem_GetHandler();
// 如果获取失败,返回 NULL
if (mem_handler == NULL) {
return NULL;
}
}
// 从内存处理器对象中获取处理器结构体指针
handler = (PyDataMem_Handler *) PyCapsule_GetPointer(
mem_handler, MEM_HANDLER_CAPSULE_NAME);
// 如果获取失败,释放内存处理器对象并返回 NULL
if (handler == NULL) {
Py_DECREF(mem_handler);
return NULL;
}
// 从处理器结构体中获取处理器版本号,转换成 Python 整数对象
version = PyLong_FromLong(handler->version);
Py_DECREF(mem_handler);
return version; // 返回处理器版本号对象
}
.\numpy\numpy\_core\src\multiarray\alloc.h
NPY_NO_EXPORT PyObject * // NPY_NO_EXPORT修饰的PyObject指针类型的函数
_get_madvise_hugepage(PyObject *NPY_UNUSED(self), PyObject *NPY_UNUSED(args)); // 获取_madvise_hugepage函数声明
NPY_NO_EXPORT PyObject * // NPY_NO_EXPORT修饰的PyObject指针类型的函数
_set_madvise_hugepage(PyObject *NPY_UNUSED(self), PyObject *enabled_obj); // 设置_madvise_hugepage函数声明
NPY_NO_EXPORT void * // NPY_NO_EXPORT修饰的void指针类型的函数
PyDataMem_UserNEW(npy_uintp sz, PyObject *mem_handler); // PyDataMem_UserNEW函数声明
NPY_NO_EXPORT void * // NPY_NO_EXPORT修饰的void指针类型的函数
PyDataMem_UserNEW_ZEROED(size_t nmemb, size_t size, PyObject *mem_handler); // PyDataMem_UserNEW_ZEROED函数声明
NPY_NO_EXPORT void // NPY_NO_EXPORT修饰的void类型的函数
PyDataMem_UserFREE(void * p, npy_uintp sd, PyObject *mem_handler); // PyDataMem_UserFREE函数声明
NPY_NO_EXPORT void * // NPY_NO_EXPORT修饰的void指针类型的函数
PyDataMem_UserRENEW(void *ptr, size_t size, PyObject *mem_handler); // PyDataMem_UserRENEW函数声明
NPY_NO_EXPORT void * // NPY_NO_EXPORT修饰的void指针类型的函数
npy_alloc_cache_dim(npy_uintp sz); // npy_alloc_cache_dim函数声明
NPY_NO_EXPORT void // NPY_NO_EXPORT修饰的void类型的函数
npy_free_cache_dim(void * p, npy_uintp sd); // npy_free_cache_dim函数声明
static inline void // 内联函数void类型
npy_free_cache_dim_obj(PyArray_Dims dims) // npy_free_cache_dim_obj函数声明
{
npy_free_cache_dim(dims.ptr, dims.len); // 调用npy_free_cache_dim函数
}
static inline void // 内联函数void类型
npy_free_cache_dim_array(PyArrayObject * arr) // npy_free_cache_dim_array函数声明
{
npy_free_cache_dim(PyArray_DIMS(arr), PyArray_NDIM(arr)); // 调用npy_free_cache_dim函数
}
extern PyDataMem_Handler default_handler; // 声明外部变量default_handler,类型为PyDataMem_Handler
extern PyObject *current_handler; // 声明外部变量current_handler,类型为PyObject指针
NPY_NO_EXPORT PyObject * // NPY_NO_EXPORT修饰的PyObject指针类型的函数
get_handler_name(PyObject *NPY_UNUSED(self), PyObject *obj); // get_handler_name函数声明
NPY_NO_EXPORT PyObject * // NPY_NO_EXPORT修饰的PyObject指针类型的函数
get_handler_version(PyObject *NPY_UNUSED(self), PyObject *obj); // get_handler_version函数声明
.\numpy\numpy\_core\src\multiarray\arrayfunction_override.c
/*
* Define NPY_NO_DEPRECATED_API to use the latest NumPy API version.
* This helps in avoiding deprecated features.
*/
/*
* Define _MULTIARRAYMODULE to include definitions specific to the multiarray module.
* This is necessary for integrating with NumPy's multiarray functionalities.
*/
#define _MULTIARRAYMODULE
#include <Python.h>
#include "structmember.h"
#include "numpy/ndarraytypes.h"
#include "get_attr_string.h"
#include "npy_import.h"
#include "npy_static_data.h"
#include "multiarraymodule.h"
#include "arrayfunction_override.h"
/*
* Get an object's __array_function__ method in the fastest way possible.
* Never raises an exception. Returns NULL if the method doesn't exist.
*/
static PyObject *
get_array_function(PyObject *obj)
{
/* Fast return for ndarray */
if (PyArray_CheckExact(obj)) {
Py_INCREF(npy_static_pydata.ndarray_array_function);
return npy_static_pydata.ndarray_array_function;
}
/*
* Lookup the __array_function__ attribute for the given object.
* If not found, return NULL. Clear any previous errors if they occurred.
*/
PyObject *array_function = PyArray_LookupSpecial(obj, npy_interned_str.array_function);
if (array_function == NULL && PyErr_Occurred()) {
PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
}
return array_function;
}
/*
* Like list.insert(), but for C arrays of PyObject*. Skips error checking.
*/
static void
pyobject_array_insert(PyObject **array, int length, int index, PyObject *item)
{
/*
* Insert an item into a C array of PyObject* at the specified index.
* Shift subsequent elements to the right to make space for the new item.
*/
for (int j = length; j > index; j--) {
array[j] = array[j - 1];
}
array[index] = item;
}
/*
* Collects arguments with __array_function__ and their corresponding methods
* in the order in which they should be tried (i.e., skipping redundant types).
* `relevant_args` is expected to have been produced by PySequence_Fast.
* Returns the number of arguments, or -1 on failure.
*/
static int
get_implementing_args_and_methods(PyObject *relevant_args,
PyObject **implementing_args,
PyObject **methods)
{
int num_implementing_args = 0;
/*
* Extract the array of items from `relevant_args` and determine its length.
*/
PyObject **items = PySequence_Fast_ITEMS(relevant_args);
Py_ssize_t length = PySequence_Fast_GET_SIZE(relevant_args);
// 遍历 items 数组中的每个元素
for (Py_ssize_t i = 0; i < length; i++) {
// 默认将当前元素视为新类型
int new_class = 1;
// 获取当前元素作为 argument
PyObject *argument = items[i];
/* 我们之前见过这种类型吗? */
// 遍历已知的实现参数列表
for (int j = 0; j < num_implementing_args; j++) {
// 检查当前 argument 是否与某个已知实现参数具有相同的类型
if (Py_TYPE(argument) == Py_TYPE(implementing_args[j])) {
// 如果找到相同类型的实现参数,标记为不是新类型
new_class = 0;
break;
}
}
// 如果是新类型
if (new_class) {
// 获取 argument 的数组函数方法
PyObject *method = get_array_function(argument);
// 如果成功获取方法
if (method != NULL) {
int arg_index;
// 检查是否超过了最大的参数数量限制
if (num_implementing_args >= NPY_MAXARGS) {
PyErr_Format(
PyExc_TypeError,
"maximum number (%d) of distinct argument types " \
"implementing __array_function__ exceeded",
NPY_MAXARGS);
Py_DECREF(method);
// 失败时跳转到 fail 标签处
goto fail;
}
/* "subclasses before superclasses, otherwise left to right" */
// 确定当前 argument 应该插入到 implementing_args 数组的位置
arg_index = num_implementing_args;
for (int j = 0; j < num_implementing_args; j++) {
PyObject *other_type;
other_type = (PyObject *)Py_TYPE(implementing_args[j]);
// 检查当前 argument 是否是已知实现参数 j 的实例
if (PyObject_IsInstance(argument, other_type)) {
arg_index = j;
break;
}
}
// 增加 argument 的引用计数并插入到 implementing_args 数组中
Py_INCREF(argument);
pyobject_array_insert(implementing_args, num_implementing_args,
arg_index, argument);
// 同样插入方法到 methods 数组中
pyobject_array_insert(methods, num_implementing_args,
arg_index, method);
// 增加实现参数的数量
++num_implementing_args;
}
}
}
// 返回找到的实现参数的数量
return num_implementing_args;
fail:
for (int j = 0; j < num_implementing_args; j++) {
Py_DECREF(implementing_args[j]);
Py_DECREF(methods[j]);
}
// 释放所有引用计数,避免内存泄漏
return -1;
}
/*
* Is this object ndarray.__array_function__?
*/
static int
is_default_array_function(PyObject *obj)
{
// 检查对象是否为 ndarray.__array_function__
return obj == npy_static_pydata.ndarray_array_function;
}
/*
* Core implementation of ndarray.__array_function__. This is exposed
* separately so we can avoid the overhead of a Python method call from
* within `implement_array_function`.
*/
NPY_NO_EXPORT PyObject *
array_function_method_impl(PyObject *func, PyObject *types, PyObject *args,
PyObject *kwargs)
{
PyObject **items = PySequence_Fast_ITEMS(types);
Py_ssize_t length = PySequence_Fast_GET_SIZE(types);
// 检查每个类型是否为 PyArray_Type 的子类
for (Py_ssize_t j = 0; j < length; j++) {
int is_subclass = PyObject_IsSubclass(
items[j], (PyObject *)&PyArray_Type);
if (is_subclass == -1) {
return NULL;
}
if (!is_subclass) {
// 如果不是,则返回 Py_NotImplemented
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
}
// 获取 func 对象的 implementation 属性
PyObject *implementation = PyObject_GetAttr(func, npy_interned_str.implementation);
if (implementation == NULL) {
return NULL;
}
// 调用 implementation 对象,并传递 args 和 kwargs
PyObject *result = PyObject_Call(implementation, args, kwargs);
Py_DECREF(implementation);
return result;
}
/*
* Calls __array_function__ on the provided argument, with a fast-path for
* ndarray.
*/
static PyObject *
call_array_function(PyObject* argument, PyObject* method,
PyObject* public_api, PyObject* types,
PyObject* args, PyObject* kwargs)
{
// 如果 method 是 ndarray 的默认 array_function,则调用 array_function_method_impl
if (is_default_array_function(method)) {
return array_function_method_impl(public_api, types, args, kwargs);
}
else {
// 否则,调用 method 对象,并传递 argument, public_api, types, args, kwargs
return PyObject_CallFunctionObjArgs(
method, argument, public_api, types, args, kwargs, NULL);
}
}
/*
* Helper to convert from vectorcall convention, since the protocol requires
* args and kwargs to be passed as tuple and dict explicitly.
* We always pass a dict, so always returns it.
*/
static int
get_args_and_kwargs(
PyObject *const *fast_args, Py_ssize_t len_args, PyObject *kwnames,
PyObject **out_args, PyObject **out_kwargs)
{
// 转换为 vectorcall 约定,显式传递 args 和 kwargs
len_args = PyVectorcall_NARGS(len_args);
PyObject *args = PyTuple_New(len_args);
PyObject *kwargs = NULL;
if (args == NULL) {
return -1;
}
// 将 fast_args 转移到 args 元组中
for (Py_ssize_t i = 0; i < len_args; i++) {
Py_INCREF(fast_args[i]);
PyTuple_SET_ITEM(args, i, fast_args[i]);
}
// 创建一个空的 kwargs 字典
kwargs = PyDict_New();
if (kwargs == NULL) {
Py_DECREF(args);
return -1;
}
# 如果关键字参数名列表不为空
if (kwnames != NULL) {
# 获取关键字参数名列表的长度
Py_ssize_t nkwargs = PyTuple_GET_SIZE(kwnames);
# 遍历关键字参数名列表
for (Py_ssize_t i = 0; i < nkwargs; i++) {
# 获取第 i 个关键字参数名
PyObject *key = PyTuple_GET_ITEM(kwnames, i);
# 获取对应的快速参数中的值(即可变参数中的值)
PyObject *value = fast_args[i+len_args];
# 将关键字参数名和对应值设置到 kwargs 字典中
if (PyDict_SetItem(kwargs, key, value) < 0) {
# 如果设置失败,释放内存并返回错误
Py_DECREF(args);
Py_DECREF(kwargs);
return -1;
}
}
}
# 将构建好的 args 和 kwargs 分别传递出去
*out_args = args;
*out_kwargs = kwargs;
# 返回成功状态
return 0;
/* 设置没有匹配类型的错误,抛出 TypeError 异常 */
npy_cache_import("numpy._core._internal",
"array_function_errmsg_formatter",
&npy_thread_unsafe_state.array_function_errmsg_formatter);
if (npy_thread_unsafe_state.array_function_errmsg_formatter != NULL) {
// 调用 array_function_errmsg_formatter 函数,生成错误信息对象
PyObject *errmsg = PyObject_CallFunctionObjArgs(
npy_thread_unsafe_state.array_function_errmsg_formatter,
public_api, types, NULL);
if (errmsg != NULL) {
// 将错误信息设置为 TypeError 异常的对象
PyErr_SetObject(PyExc_TypeError, errmsg);
Py_DECREF(errmsg);
}
}
}
/*
* 实现了 __array_function__ 协议,用于 C 数组创建函数。
* 在 NEP-18 的基础上添加,旨在以最小的分发开销实现 NEP-35。
*
* 调用者必须确保 `like != Py_None` 或 `like == NULL`。
*/
NPY_NO_EXPORT PyObject *
array_implement_c_array_function_creation(
const char *function_name, PyObject *like,
PyObject *args, PyObject *kwargs,
PyObject *const *fast_args, Py_ssize_t len_args, PyObject *kwnames)
{
PyObject *dispatch_types = NULL;
PyObject *numpy_module = NULL;
PyObject *public_api = NULL;
PyObject *result = NULL;
/* 如果 `like` 没有实现 `__array_function__`,抛出 `TypeError` */
PyObject *method = get_array_function(like);
if (method == NULL) {
return PyErr_Format(PyExc_TypeError,
"The `like` argument must be an array-like that "
"implements the `__array_function__` protocol.");
}
if (is_default_array_function(method)) {
/*
* 返回 Py_NotImplemented 的借用引用,以将处理权返回给原始函数。
*/
Py_DECREF(method);
return Py_NotImplemented;
}
/* 需要为 `__array_function__` 准备 args 和 kwargs(在不使用时)。 */
if (fast_args != NULL) {
assert(args == NULL);
assert(kwargs == NULL);
if (get_args_and_kwargs(
fast_args, len_args, kwnames, &args, &kwargs) < 0) {
goto finish;
}
}
else {
Py_INCREF(args);
Py_INCREF(kwargs);
}
dispatch_types = PyTuple_Pack(1, Py_TYPE(like));
if (dispatch_types == NULL) {
goto finish;
}
/* 在关键字参数中必须包含 like 参数,移除它 */
if (PyDict_DelItem(kwargs, npy_interned_str.like) < 0) {
goto finish;
}
/* 获取实际的符号(目前的长途方法) */
numpy_module = PyImport_Import(npy_interned_str.numpy);
if (numpy_module == NULL) {
goto finish;
}
/* 获取 numpy 模块中的函数名对应的公共 API */
public_api = PyObject_GetAttrString(numpy_module, function_name);
Py_DECREF(numpy_module);
if (public_api == NULL) {
goto finish;
}
# 检查 public_api 是否为可调用对象,如果不是则抛出运行时错误
if (!PyCallable_Check(public_api)) {
# 格式化错误信息,指出 numpy.function_name 不可调用
PyErr_Format(PyExc_RuntimeError,
"numpy.%s is not callable.", function_name);
# 跳转到完成处理的标签位置
goto finish;
}
# 调用 call_array_function 函数,传递 like, method, public_api, dispatch_types, args, kwargs 参数
result = call_array_function(like, method,
public_api, dispatch_types, args, kwargs);
# 如果 call_array_function 返回 Py_NotImplemented,应该不会发生,但如果发生则处理
if (result == Py_NotImplemented) {
/* 这实际上不应该发生,因为只有一种类型,但是... */
# 释放 result 对象的引用计数
Py_DECREF(result);
# 将 result 置为 NULL
result = NULL;
# 设置没有匹配类型错误,传递 public_api 和 dispatch_types 参数
set_no_matching_types_error(public_api, dispatch_types);
}
finish:
# 释放 method 对象的引用计数
Py_DECREF(method);
# 释放 args 对象的引用计数,如果为空则无影响
Py_XDECREF(args);
# 释放 kwargs 对象的引用计数,如果为空则无影响
Py_XDECREF(kwargs);
# 释放 dispatch_types 对象的引用计数,如果为空则无影响
Py_XDECREF(dispatch_types);
# 释放 public_api 对象的引用计数,如果为空则无影响
Py_XDECREF(public_api);
# 返回 result 对象
return result;
/*
* Python wrapper for get_implementing_args_and_methods, for testing purposes.
*/
# Python函数array__get_implementing_args的C扩展实现
NPY_NO_EXPORT PyObject *
array__get_implementing_args(
PyObject *NPY_UNUSED(dummy), PyObject *positional_args)
{
PyObject *relevant_args; // 相关参数对象
PyObject *implementing_args[NPY_MAXARGS]; // 实现参数数组
PyObject *array_function_methods[NPY_MAXARGS]; // 数组函数方法数组
PyObject *result = NULL; // 结果对象,默认为NULL
// 解析传入的参数元组,获取relevant_args对象
if (!PyArg_ParseTuple(positional_args, "O:array__get_implementing_args",
&relevant_args)) {
return NULL;
}
// 快速转换relevant_args为一个Python序列,用于迭代
relevant_args = PySequence_Fast(
relevant_args,
"dispatcher for __array_function__ did not return an iterable");
if (relevant_args == NULL) {
return NULL;
}
// 获取相关参数和数组函数方法
int num_implementing_args = get_implementing_args_and_methods(
relevant_args, implementing_args, array_function_methods);
if (num_implementing_args == -1) {
goto cleanup;
}
/* create a Python object for implementing_args */
// 创建一个Python列表对象,用于存放实现参数
result = PyList_New(num_implementing_args);
if (result == NULL) {
goto cleanup;
}
// 将实现参数复制到结果列表中
for (int j = 0; j < num_implementing_args; j++) {
PyObject *argument = implementing_args[j];
Py_INCREF(argument);
PyList_SET_ITEM(result, j, argument);
}
cleanup:
// 清理工作:减少实现参数和数组函数方法的引用计数,释放relevant_args
for (int j = 0; j < num_implementing_args; j++) {
Py_DECREF(implementing_args[j]);
Py_DECREF(array_function_methods[j]);
}
Py_DECREF(relevant_args);
return result; // 返回结果对象
}
typedef struct {
PyObject_HEAD
vectorcallfunc vectorcall; // 向量调用函数
PyObject *dict; // 字典对象
PyObject *relevant_arg_func; // 相关参数函数
PyObject *default_impl; // 默认实现
/* The following fields are used to clean up TypeError messages only: */
PyObject *dispatcher_name; // 分发器名称
PyObject *public_name; // 公共名称
} PyArray_ArrayFunctionDispatcherObject;
static void
dispatcher_dealloc(PyArray_ArrayFunctionDispatcherObject *self)
{
// 释放PyArray_ArrayFunctionDispatcherObject对象
Py_CLEAR(self->relevant_arg_func);
Py_CLEAR(self->default_impl);
Py_CLEAR(self->dict);
Py_CLEAR(self->dispatcher_name);
Py_CLEAR(self->public_name);
PyObject_FREE(self); // 释放内存
}
static void
fix_name_if_typeerror(PyArray_ArrayFunctionDispatcherObject *self)
{
// 如果当前异常不是TypeError,则直接返回
if (!PyErr_ExceptionMatches(PyExc_TypeError)) {
return;
}
PyObject *exc, *val, *tb, *message; // 异常对象、值、回溯和消息对象
PyErr_Fetch(&exc, &val, &tb); // 获取当前异常信息
if (!PyUnicode_CheckExact(val)) {
/*
* 如果 val 不是 PyUnicode 对象,我们期望错误未规范化,
* 但可能并非总是如此,因此如果它不是字符串,则规范化并获取 args[0]。
*/
PyErr_NormalizeException(&exc, &val, &tb);
// 获取异常对象的 "args" 属性,应该返回一个元组
PyObject *args = PyObject_GetAttrString(val, "args");
if (args == NULL || !PyTuple_CheckExact(args)
|| PyTuple_GET_SIZE(args) != 1) {
Py_XDECREF(args);
// 如果获取失败或者 args 不是一个单元素元组,则恢复原始错误状态
goto restore_error;
}
// 获取元组 args 中的第一个元素作为错误消息
message = PyTuple_GET_ITEM(args, 0);
Py_INCREF(message);
Py_DECREF(args);
// 如果消息不是字符串类型,则恢复原始错误状态
if (!PyUnicode_CheckExact(message)) {
Py_DECREF(message);
goto restore_error;
}
}
else {
// 如果 val 是 PyUnicode 对象,则直接增加其引用计数
Py_INCREF(val);
message = val;
}
// 比较 message 和 self->dispatcher_name 的尾部,期望匹配
Py_ssize_t cmp = PyUnicode_Tailmatch(
message, self->dispatcher_name, 0, -1, -1);
// 如果不匹配或者出错,则恢复原始错误状态
if (cmp <= 0) {
Py_DECREF(message);
goto restore_error;
}
// 将 message 中的 self->dispatcher_name 替换为 self->public_name
Py_SETREF(message, PyUnicode_Replace(
message, self->dispatcher_name, self->public_name, 1));
// 如果替换失败,则恢复原始错误状态
if (message == NULL) {
goto restore_error;
}
// 设置异常类型为 TypeError,消息为 message
PyErr_SetObject(PyExc_TypeError, message);
Py_DECREF(exc);
Py_XDECREF(val);
Py_XDECREF(tb);
Py_DECREF(message);
return;
restore_error:
/* 替换未成功,因此恢复原始错误 */
PyErr_Restore(exc, val, tb);
# 定义 Python C 扩展模块中的一个函数,用于分派调用向量化数组函数的操作
static PyObject *
dispatcher_vectorcall(PyArray_ArrayFunctionDispatcherObject *self,
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
PyObject *result = NULL;
PyObject *types = NULL;
PyObject *relevant_args = NULL;
PyObject *public_api;
# 如果定义了 relevant_arg_func 函数,则使用 self 作为公共 API
if (self->relevant_arg_func != NULL) {
public_api = (PyObject *)self;
# 调用 relevant_arg_func 函数,获取返回的参数列表
relevant_args = PyObject_Vectorcall(
self->relevant_arg_func, args, len_args, kwnames);
if (relevant_args == NULL) {
# 如果获取参数列表失败,修复错误名称并返回空值
fix_name_if_typeerror(self);
return NULL;
}
# 将 relevant_args 转换为快速序列对象
Py_SETREF(relevant_args, PySequence_Fast(relevant_args,
"dispatcher for __array_function__ did not return an iterable"));
if (relevant_args == NULL) {
return NULL;
}
# 获取实现参数和方法,并存储到相应数组中
num_implementing_args = get_implementing_args_and_methods(
relevant_args, implementing_args, array_function_methods);
if (num_implementing_args < 0) {
Py_DECREF(relevant_args);
return NULL;
}
}
else {
# 对于像 from Python 中的 like= 分派,公共符号是默认实现
public_api = self->default_impl;
# 如果没有传入参数,报错并返回空值
if (PyVectorcall_NARGS(len_args) == 0) {
PyErr_Format(PyExc_TypeError,
"`like` argument dispatching, but first argument is not "
"positional in call to %S.", self->default_impl);
return NULL;
}
# 获取第一个参数的数组函数方法
array_function_methods[0] = get_array_function(args[0]);
if (array_function_methods[0] == NULL) {
return PyErr_Format(PyExc_TypeError,
"The `like` argument must be an array-like that "
"implements the `__array_function__` protocol.");
}
num_implementing_args = 1;
implementing_args[0] = args[0];
Py_INCREF(implementing_args[0]);
# 不传递 like 参数
len_args = PyVectorcall_NARGS(len_args) - 1;
len_args |= PY_VECTORCALL_ARGUMENTS_OFFSET;
args++;
}
# 处理没有覆盖情况的典型情况,检查是否有重载函数
int any_overrides = 0;
// 遍历实现参数数组,检查是否有非默认数组函数
for (int j = 0; j < num_implementing_args; j++) {
if (!is_default_array_function(array_function_methods[j])) {
// 如果有任何一个函数不是默认的数组函数,设置标志为真并跳出循环
any_overrides = 1;
break;
}
}
// 如果没有函数重写,默认调用实现
if (!any_overrides) {
/* 直接调用实际的实现函数。 */
result = PyObject_Vectorcall(self->default_impl, args, len_args, kwnames);
// 跳转到清理操作
goto cleanup;
}
/* 找到参数和关键字参数作为元组和字典,因为我们要传递它们出去: */
if (get_args_and_kwargs(
args, len_args, kwnames, &packed_args, &packed_kwargs) < 0) {
// 如果获取参数和关键字参数失败,跳转到清理操作
goto cleanup;
}
/*
* 为类型创建一个 Python 对象。
* 我们使用元组,因为它是创建速度最快的 Python 集合,
* 并且额外的好处是它是不可变的。
*/
types = PyTuple_New(num_implementing_args);
if (types == NULL) {
// 如果创建元组失败,跳转到清理操作
goto cleanup;
}
// 填充元组,将每个实现参数的类型添加到元组中
for (int j = 0; j < num_implementing_args; j++) {
PyObject *arg_type = (PyObject *)Py_TYPE(implementing_args[j]);
Py_INCREF(arg_type);
PyTuple_SET_ITEM(types, j, arg_type);
}
/* 调用 __array_function__ 方法 */
for (int j = 0; j < num_implementing_args; j++) {
PyObject *argument = implementing_args[j];
PyObject *method = array_function_methods[j];
result = call_array_function(
argument, method, public_api, types,
packed_args, packed_kwargs);
if (result == Py_NotImplemented) {
/* 尝试下一个方法 */
Py_DECREF(result);
result = NULL;
}
else {
/* 获得了有效的结果,或者引发了异常。 */
goto cleanup;
}
}
// 设置没有匹配类型的错误
set_no_matching_types_error(public_api, types);
cleanup:
# 清理阶段,释放实现参数和数组方法数组中的引用计数
for (int j = 0; j < num_implementing_args; j++) {
Py_DECREF(implementing_args[j]);
Py_DECREF(array_function_methods[j]);
}
// 释放打包参数和打包关键字参数的引用计数
Py_XDECREF(packed_args);
Py_XDECREF(packed_kwargs);
// 释放类型对象的引用计数
Py_XDECREF(types);
// 释放相关参数的引用计数
Py_XDECREF(relevant_args);
// 返回计算结果
return result;
}
static PyObject *
dispatcher_new(PyTypeObject *NPY_UNUSED(cls), PyObject *args, PyObject *kwargs)
{
PyArray_ArrayFunctionDispatcherObject *self;
// 创建一个新的对象,分配内存并初始化为指定类型
self = PyObject_New(
PyArray_ArrayFunctionDispatcherObject,
&PyArrayFunctionDispatcher_Type);
if (self == NULL) {
// 内存分配失败,返回内存错误异常
return PyErr_NoMemory();
}
// 定义关键字参数列表
char *kwlist[] = {"", "", NULL};
// 解析输入参数并设置实例属性
if (!PyArg_ParseTupleAndKeywords(
args, kwargs, "OO:_ArrayFunctionDispatcher", kwlist,
&self->relevant_arg_func, &self->default_impl)) {
// 解析失败时释放对象并返回空
Py_DECREF(self);
return NULL;
}
// 设置向量调用函数
self->vectorcall = (vectorcallfunc)dispatcher_vectorcall;
// 增加默认实现的引用计数
Py_INCREF(self->default_impl);
// 初始化字典为空
self->dict = NULL;
self->dispatcher_name = NULL;
self->public_name = NULL;
// 如果相关参数函数是 Py_None,则清除引用
if (self->relevant_arg_func == Py_None) {
/* NULL in the relevant arg function means we use `like=` */
Py_CLEAR(self->relevant_arg_func);
}
else {
// 增加相关参数函数的引用计数,并获取其限定名称
Py_INCREF(self->relevant_arg_func);
self->dispatcher_name = PyObject_GetAttrString(
self->relevant_arg_func, "__qualname__");
if (self->dispatcher_name == NULL) {
// 获取失败时释放对象并返回空
Py_DECREF(self);
return NULL;
}
// 获取默认实现的限定名称
self->public_name = PyObject_GetAttrString(
self->default_impl, "__qualname__");
if (self->public_name == NULL) {
// 获取失败时释放对象并返回空
Py_DECREF(self);
return NULL;
}
}
// 创建新的空字典对象
self->dict = PyDict_New();
if (self->dict == NULL) {
// 字典创建失败时释放对象并返回空
Py_DECREF(self);
return NULL;
}
// 返回创建的对象实例
return (PyObject *)self;
}
static PyObject *
dispatcher_str(PyArray_ArrayFunctionDispatcherObject *self)
{
// 返回默认实现对象的字符串表示形式
return PyObject_Str(self->default_impl);
}
static PyObject *
dispatcher_repr(PyObject *self)
{
// 获取对象的名称属性
PyObject *name = PyObject_GetAttrString(self, "__name__");
if (name == NULL) {
// 获取失败时返回空
return NULL;
}
// 格式化字符串表示为函数形式
return PyUnicode_FromFormat("<function %S at %p>", name, self);
}
static PyObject *
func_dispatcher___get__(PyObject *self, PyObject *obj, PyObject *cls)
{
if (obj == NULL) {
// 如果对象为空,表现为静态方法,无需绑定,增加自身引用计数并返回自身
Py_INCREF(self);
return self;
}
// 创建一个新的方法对象并返回
return PyMethod_New(self, obj);
}
static PyObject *
dispatcher_get_implementation(
PyArray_ArrayFunctionDispatcherObject *self, void *NPY_UNUSED(closure))
{
// 增加默认实现对象的引用计数并返回
Py_INCREF(self->default_impl);
return self->default_impl;
}
static PyObject *
dispatcher_reduce(PyObject *self, PyObject *NPY_UNUSED(args))
{
// 在此处实现对象的减少协议
// (此处需补充完整该函数的代码和注释)
}
# 获取对象的 "__qualname__" 属性,并返回其值
return PyObject_GetAttrString(self, "__qualname__");
# 定义一个静态的 PyMethodDef 结构体数组,用于描述函数调度器对象的方法
static struct PyMethodDef func_dispatcher_methods[] = {
# "__reduce__" 方法,指定为 dispatcher_reduce 函数处理,不带参数,无额外信息
{"__reduce__",
(PyCFunction)dispatcher_reduce, METH_NOARGS, NULL},
# 结束标志,表示方法列表的结束
{NULL, NULL, 0, NULL}
};
# 定义一个静态的 PyGetSetDef 结构体数组,用于描述函数调度器对象的属性获取和设置
static struct PyGetSetDef func_dispatcher_getset[] = {
# "__dict__" 属性,使用 PyObject_GenericGetDict 获取,不带额外信息
{"__dict__", &PyObject_GenericGetDict, 0, NULL, 0},
# "_implementation" 属性,使用 dispatcher_get_implementation 函数获取,不带额外信息
{"_implementation", (getter)&dispatcher_get_implementation, 0, NULL, 0},
# 结束标志,表示属性列表的结束
{0, 0, 0, 0, 0}
};
# 定义一个非导出的 PyTypeObject 结构体,描述数组函数调度器对象的类型信息
NPY_NO_EXPORT PyTypeObject PyArrayFunctionDispatcher_Type = {
# 初始化头部信息,没有基类,大小为 0
PyVarObject_HEAD_INIT(NULL, 0)
# 类型名称为 "numpy._ArrayFunctionDispatcher"
.tp_name = "numpy._ArrayFunctionDispatcher",
# 类型的基本大小,为 PyArray_ArrayFunctionDispatcherObject 结构体的大小
.tp_basicsize = sizeof(PyArray_ArrayFunctionDispatcherObject),
# tp_dictoffset 偏移量,指向对象中字典的位置,用于快速访问 __dict__ 属性
.tp_dictoffset = offsetof(PyArray_ArrayFunctionDispatcherObject, dict),
# 析构函数,释放对象时调用 dispatcher_dealloc 函数
.tp_dealloc = (destructor)dispatcher_dealloc,
# 创建新对象的函数,调用 dispatcher_new 函数
.tp_new = (newfunc)dispatcher_new,
# 字符串表示函数,调用 dispatcher_str 函数
.tp_str = (reprfunc)dispatcher_str,
# repr 表示函数,调用 dispatcher_repr 函数
.tp_repr = (reprfunc)dispatcher_repr,
# 类型标志,包括默认标志、支持向量调用、方法描述符
.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_VECTORCALL
| Py_TPFLAGS_METHOD_DESCRIPTOR),
# 方法定义数组,指向 func_dispatcher_methods
.tp_methods = func_dispatcher_methods,
# 属性获取和设置数组,指向 func_dispatcher_getset
.tp_getset = func_dispatcher_getset,
# 描述符获取函数,调用 func_dispatcher___get__ 函数
.tp_descr_get = func_dispatcher___get__,
# 调用函数,使用 PyVectorcall_Call 处理
.tp_call = &PyVectorcall_Call,
# 向量调用偏移量,指向 vectorcall 字段的位置
.tp_vectorcall_offset = offsetof(PyArray_ArrayFunctionDispatcherObject, vectorcall),
};
.\numpy\numpy\_core\src\multiarray\arrayfunction_override.h
// 如果没有定义 NUMPY_CORE_SRC_MULTIARRAY_ARRAYFUNCTION_OVERRIDE_H_,则执行以下代码
// 声明 PyArrayFunctionDispatcher_Type,作为 extern 外部变量
extern NPY_NO_EXPORT PyTypeObject PyArrayFunctionDispatcher_Type;
// 定义 array__get_implementing_args 函数,返回 PyObject 指针
NPY_NO_EXPORT PyObject *
array__get_implementing_args(
PyObject *NPY_UNUSED(dummy), PyObject *positional_args);
// 定义 array_implement_c_array_function_creation 函数,返回 PyObject 指针
NPY_NO_EXPORT PyObject *
array_implement_c_array_function_creation(
const char *function_name, PyObject *like,
PyObject *args, PyObject *kwargs,
PyObject *const *fast_args, Py_ssize_t len_args, PyObject *kwnames);
// 定义 array_function_method_impl 函数,返回 PyObject 指针
NPY_NO_EXPORT PyObject *
array_function_method_impl(PyObject *func, PyObject *types, PyObject *args,
PyObject *kwargs);
// 结束条件,结束 ifndef 指令
.\numpy\numpy\_core\src\multiarray\arrayobject.c
/*
提供多维数组作为Python的基本对象类型。
基于原始的Numeric实现
版权所有 (c) 1995, 1996, 1997 Jim Hugunin, hugunin@mit.edu
还包括了来自1995-2004年间许多Numeric Python开发者的贡献
2005年进行了大幅修改,受到Numarray的启发
作者:
Travis Oliphant, oliphant@ee.byu.edu
布里格姆扬大学
维护者邮箱: oliphant.travis@ieee.org
Numarray的设计(提供了指导)由
Space Science Telescope Institute
(J. Todd Miller, Perry Greenfield, Rick White)
*/
/*NUMPY_API
计算数组的大小(以项数表示)
*/
NPY_NO_EXPORT npy_intp
PyArray_Size(PyObject *op)
{
if (PyArray_Check(op)) {
return PyArray_SIZE((PyArrayObject *)op);
}
else {
return 0;
}
}
/*NUMPY_API */
NPY_NO_EXPORT int
PyArray_SetUpdateIfCopyBase(PyArrayObject *arr, PyArrayObject *base)
{
/* 2021-Dec-15 1.23*/
PyErr_SetString(PyExc_RuntimeError,
"PyArray_SetUpdateIfCopyBase is disabled, use "
"PyArray_SetWritebackIfCopyBase instead, and be sure to call "
"PyArray_ResolveWritebackIfCopy before the array is deallocated, "
"i.e. before the last call to Py_DECREF. If cleaning up from an "
"error, PyArray_DiscardWritebackIfCopy may be called instead to "
"throw away the scratch buffer.");
return -1;
}
/*NUMPY_API
*
* 前提条件:'arr' 是 'base' 的一个副本(尽管可能具有不同的步幅、顺序等)。
* 此函数设置WRITEBACKIFCOPY标志和->base指针在'arr'上,
* 调用PyArray_ResolveWritebackIfCopy在释放数组之前将任何更改复制回'base'。
*
* 引用计数 'base'。
*
* 成功返回0,失败返回-1。
*/
NPY_NO_EXPORT int
PyArray_SetWritebackIfCopyBase(PyArrayObject *arr, PyArrayObject *base)
{
if (base == NULL) {
PyErr_SetString(PyExc_ValueError,
"Cannot WRITEBACKIFCOPY to NULL array");
return -1;
}
if (PyArray_BASE(arr) != NULL) {
PyErr_SetString(PyExc_ValueError,
"Cannot set array with existing base to WRITEBACKIFCOPY");
goto fail;
}
if (PyArray_FailUnlessWriteable(base, "WRITEBACKIFCOPY base") < 0) {
goto fail;
}
/*
* Any writes to 'arr' will magically turn into writes to 'base', so we
* should warn if necessary.
*/
if (PyArray_FLAGS(base) & NPY_ARRAY_WARN_ON_WRITE) {
PyArray_ENABLEFLAGS(arr, NPY_ARRAY_WARN_ON_WRITE);
}
/*
* Unlike PyArray_SetBaseObject, we do not compress the chain of base
* references.
*/
((PyArrayObject_fields *)arr)->base = (PyObject *)base;
PyArray_ENABLEFLAGS(arr, NPY_ARRAY_WRITEBACKIFCOPY);
PyArray_CLEARFLAGS(base, NPY_ARRAY_WRITEABLE);
return 0;
fail:
Py_DECREF(base);
return -1;
/*NUMPY_API
* Sets the 'base' attribute of the array. This steals a reference
* to 'obj'.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
PyArray_SetBaseObject(PyArrayObject *arr, PyObject *obj)
{
// 如果传入的对象是空指针,抛出数值错误异常并返回 -1
if (obj == NULL) {
PyErr_SetString(PyExc_ValueError,
"Cannot set the NumPy array 'base' "
"dependency to NULL after initialization");
return -1;
}
/*
* 允许仅设置一次基础对象。一旦设置了拥有数据的对象,再次更改就不合理了。
*/
if (PyArray_BASE(arr) != NULL) {
// 引用计数减一,清理对象,并抛出数值错误异常
Py_DECREF(obj);
PyErr_SetString(PyExc_ValueError,
"Cannot set the NumPy array 'base' "
"dependency more than once");
return -1;
}
/*
* 不允许视图之间无限的链式依赖,始终将基础对象设置为数据的第一个拥有者。
* 即,第一个不是数组的对象,或者第一个拥有自己数据的对象。
*/
while (PyArray_Check(obj) && (PyObject *)arr != obj) {
PyArrayObject *obj_arr = (PyArrayObject *)obj;
PyObject *tmp;
/* 如果视图具有 NPY_ARRAY_WARN_ON_WRITE 标志,传播该警告 */
if (PyArray_FLAGS(obj_arr) & NPY_ARRAY_WARN_ON_WRITE) {
PyArray_ENABLEFLAGS(arr, NPY_ARRAY_WARN_ON_WRITE);
}
/* 如果此数组拥有自己的数据,则停止合并 */
if (PyArray_CHKFLAGS(obj_arr, NPY_ARRAY_OWNDATA)) {
break;
}
tmp = PyArray_BASE(obj_arr);
/* 如果没有基础对象,则停止合并 */
if (tmp == NULL) {
break;
}
/* 当新基础对象的类型不同时停止合并(即,不同的子类) */
if (Py_TYPE(tmp) != Py_TYPE(arr)) {
break;
}
Py_INCREF(tmp);
Py_DECREF(obj);
obj = tmp;
}
/* 禁止循环引用 */
if ((PyObject *)arr == obj) {
Py_DECREF(obj);
PyErr_SetString(PyExc_ValueError,
"Cannot create a circular NumPy array 'base' dependency");
return -1;
}
// 设置数组对象的 base 属性为 obj
((PyArrayObject_fields *)arr)->base = obj;
return 0;
}
/**
* Assign an arbitrary object a NumPy array. This is largely basically
* identical to PyArray_FromAny, but assigns directly to the output array.
*
* @param dest Array to be written to
* @param src_object Object to be assigned, array-coercion rules apply.
* @return 0 on success -1 on failures.
*/
/*NUMPY_API*/
NPY_NO_EXPORT int
PyArray_CopyObject(PyArrayObject *dest, PyObject *src_object)
{
int ret = 0;
PyArrayObject *view;
PyArray_Descr *dtype = NULL;
int ndim;
npy_intp dims[NPY_MAXDIMS];
coercion_cache_obj *cache = NULL;
/*
* We have to set the maximum number of dimensions here to support
* sequences within object arrays.
*/
/*
* 在这里设置最大的维度数以支持对象数组中的序列。
*/
ndim = PyArray_DiscoverDTypeAndShape(src_object,
PyArray_NDIM(dest), dims, &cache,
NPY_DTYPE(PyArray_DESCR(dest)), PyArray_DESCR(dest), &dtype, 1, NULL);
if (ndim < 0) {
return -1;
}
if (cache != NULL && !(cache->sequence)) {
/* 输入是数组或数组对象,因此可以直接赋值 */
assert(cache->converted_obj == src_object);
view = (PyArrayObject *)cache->arr_or_sequence;
Py_DECREF(dtype);
ret = PyArray_AssignArray(dest, view, NULL, NPY_UNSAFE_CASTING);
npy_free_coercion_cache(cache);
return ret;
}
/*
* 可能需要广播,因为形状不匹配,此时先创建临时数组,并填充后再赋值
*/
if (ndim != PyArray_NDIM(dest) ||
!PyArray_CompareLists(PyArray_DIMS(dest), dims, ndim)) {
/*
* 可能需要广播,因此先分配给一个视图。
* 这种情况可能会导致后面的形状不匹配错误。
*/
assert (ndim <= PyArray_NDIM(dest)); /* 在发现期间可能会出错 */
view = (PyArrayObject *) PyArray_NewFromDescr(
&PyArray_Type, dtype, ndim, dims, NULL, NULL,
PyArray_FLAGS(dest) & NPY_ARRAY_F_CONTIGUOUS, NULL);
if (view == NULL) {
npy_free_coercion_cache(cache);
return -1;
}
}
else {
Py_DECREF(dtype);
view = dest;
}
if (cache == NULL) {
/* 单个(非数组)项,立即赋值 */
if (PyArray_Pack(
PyArray_DESCR(view), PyArray_DATA(view), src_object) < 0) {
goto fail;
}
}
else {
if (PyArray_AssignFromCache(view, cache) < 0) {
goto fail;
}
}
if (view == dest) {
return 0;
}
ret = PyArray_AssignArray(dest, view, NULL, NPY_UNSAFE_CASTING);
Py_DECREF(view);
return ret;
fail:
if (view != dest) {
Py_DECREF(view);
}
return -1;
/*NUMPY_API
*
* If WRITEBACKIFCOPY and self has data, reset the base WRITEABLE flag,
* copy the local data to base, release the local data, and set flags
* appropriately. Return 0 if not relevant, 1 if success, < 0 on failure
*/
NPY_NO_EXPORT int
PyArray_ResolveWritebackIfCopy(PyArrayObject * self)
{
/* 将输入的 PyArrayObject 转换为 PyArrayObject_fields 类型 */
PyArrayObject_fields *fa = (PyArrayObject_fields *)self;
/* 检查 fa 和 fa->base 是否存在 */
if (fa && fa->base) {
/* 检查是否设置了 NPY_ARRAY_WRITEBACKIFCOPY 标志 */
if (fa->flags & NPY_ARRAY_WRITEBACKIFCOPY) {
/*
* WRITEBACKIFCOPY 意味着 fa->base 的数据
* 应该更新为 self 的内容。
* fa->base->flags 不是 WRITEABLE 以保护这种关系,
* 解锁它。
*/
int retval = 0;
/* 启用 fa->base 的 WRITEABLE 标志 */
PyArray_ENABLEFLAGS(((PyArrayObject *)fa->base),
NPY_ARRAY_WRITEABLE);
/* 清除 self 的 NPY_ARRAY_WRITEBACKIFCOPY 标志 */
PyArray_CLEARFLAGS(self, NPY_ARRAY_WRITEBACKIFCOPY);
/* 将 self 的数据复制到 fa->base */
retval = PyArray_CopyAnyInto((PyArrayObject *)fa->base, self);
/* 释放 fa->base 的引用 */
Py_DECREF(fa->base);
fa->base = NULL;
/* 如果复制失败,返回负值 */
if (retval < 0) {
/* 这不应该发生,两份数据怎么会不同步? */
return retval;
}
/* 成功复制数据,返回 1 */
return 1;
}
}
/* 没有满足条件的情况,返回 0 */
return 0;
}
/*********************** end C-API functions **********************/
/* dealloc must not raise an error, best effort try to write
to stderr and clear the error
*/
/* 在 dealloc 中不应该引发错误,尽力尝试写入 stderr 并清除错误 */
static inline void
WARN_IN_DEALLOC(PyObject* warning, const char * msg) {
/* 如果发出警告时出错 */
if (PyErr_WarnEx(warning, msg, 1) < 0) {
PyObject * s;
/* 创建字符串 "array_dealloc" */
s = PyUnicode_FromString("array_dealloc");
/* 如果成功创建字符串 */
if (s) {
/* 写入无法处理的异常 */
PyErr_WriteUnraisable(s);
/* 释放字符串对象的引用 */
Py_DECREF(s);
}
else {
/* 如果创建字符串失败,写入无法处理的异常 */
PyErr_WriteUnraisable(Py_None);
}
}
}
/* array object functions */
/* 数组对象的函数 */
static void
array_dealloc(PyArrayObject *self)
{
/* 将输入的 PyArrayObject 转换为 PyArrayObject_fields 类型 */
PyArrayObject_fields *fa = (PyArrayObject_fields *)self;
/* 如果释放缓冲区信息时出错 */
if (_buffer_info_free(fa->_buffer_info, (PyObject *)self) < 0) {
/* 写入无法处理的异常 */
PyErr_WriteUnraisable(NULL);
}
/* 清除弱引用列表 */
if (fa->weakreflist != NULL) {
PyObject_ClearWeakRefs((PyObject *)self);
}
}
if (fa->base) {
int retval;
if (PyArray_FLAGS(self) & NPY_ARRAY_WRITEBACKIFCOPY)
{
char const * msg = "WRITEBACKIFCOPY detected in array_dealloc. "
" Required call to PyArray_ResolveWritebackIfCopy or "
"PyArray_DiscardWritebackIfCopy is missing.";
/*
* 防止两次达到引用计数为0,从而递归进入dealloc。
* 增加 sys.gettotalrefcount,但实际路径不应被执行。
*/
Py_INCREF(self);
WARN_IN_DEALLOC(PyExc_RuntimeWarning, msg);
retval = PyArray_ResolveWritebackIfCopy(self);
if (retval < 0)
{
PyErr_Print();
PyErr_Clear();
}
}
/*
* 如果 fa->base 非空,则需要 DECREF 它 —— 可能是视图或缓冲区对象
*/
Py_XDECREF(fa->base);
}
if ((fa->flags & NPY_ARRAY_OWNDATA) && fa->data) {
/* 释放任何内部引用 */
if (PyDataType_REFCHK(fa->descr)) {
if (PyArray_ClearArray(self) < 0) {
PyErr_WriteUnraisable(NULL);
}
}
if (fa->mem_handler == NULL) {
char const *msg = "Trying to dealloc data, but a memory policy "
"is not set. If you take ownership of the data, you must "
"set a base owning the data (e.g. a PyCapsule).";
WARN_IN_DEALLOC(PyExc_RuntimeWarning, msg);
// 猜测使用 malloc/free ???
free(fa->data);
}
else {
size_t nbytes = PyArray_NBYTES(self);
if (nbytes == 0) {
nbytes = 1;
}
PyDataMem_UserFREE(fa->data, nbytes, fa->mem_handler);
Py_DECREF(fa->mem_handler);
}
}
/* 必须匹配 PyArray_NewFromDescr 中的分配 */
npy_free_cache_dim(fa->dimensions, 2 * fa->nd);
Py_DECREF(fa->descr);
Py_TYPE(self)->tp_free((PyObject *)self);
/*NUMPY_API
* Prints the raw data of the ndarray in a form useful for debugging
* low-level C issues.
*/
// 定义一个函数 PyArray_DebugPrint,用于打印 NumPy ndarray 的原始数据,便于低级别 C 问题的调试
NPY_NO_EXPORT void
PyArray_DebugPrint(PyArrayObject *obj)
{
int i;
// 将输入的 PyArrayObject 转换为 PyArrayObject_fields 类型的指针 fobj
PyArrayObject_fields *fobj = (PyArrayObject_fields *)obj;
// 打印一条分隔线,指示 ndarray 的内存地址
printf("-------------------------------------------------------\n");
printf(" Dump of NumPy ndarray at address %p\n", obj);
// 如果 obj 为 NULL,则输出相应的消息并返回
if (obj == NULL) {
printf(" It's NULL!\n");
printf("-------------------------------------------------------\n");
fflush(stdout);
return;
}
// 打印 ndarray 的维度信息
printf(" ndim : %d\n", fobj->nd);
// 打印 ndarray 的形状信息
printf(" shape :");
for (i = 0; i < fobj->nd; ++i) {
printf(" %" NPY_INTP_FMT, fobj->dimensions[i]);
}
printf("\n");
// 打印 ndarray 的数据类型信息
printf(" dtype : ");
PyObject_Print((PyObject *)fobj->descr, stdout, 0);
printf("\n");
// 打印 ndarray 的数据起始地址
printf(" data : %p\n", fobj->data);
// 打印 ndarray 的跨步信息
printf(" strides:");
for (i = 0; i < fobj->nd; ++i) {
printf(" %" NPY_INTP_FMT, fobj->strides[i]);
}
printf("\n");
// 打印 ndarray 的基类对象的地址
printf(" base : %p\n", fobj->base);
// 打印 ndarray 的 flags 信息,包括内存布局等属性
printf(" flags :");
if (fobj->flags & NPY_ARRAY_C_CONTIGUOUS)
printf(" NPY_C_CONTIGUOUS");
if (fobj->flags & NPY_ARRAY_F_CONTIGUOUS)
printf(" NPY_F_CONTIGUOUS");
if (fobj->flags & NPY_ARRAY_OWNDATA)
printf(" NPY_OWNDATA");
if (fobj->flags & NPY_ARRAY_ALIGNED)
printf(" NPY_ALIGNED");
if (fobj->flags & NPY_ARRAY_WRITEABLE)
printf(" NPY_WRITEABLE");
if (fobj->flags & NPY_ARRAY_WRITEBACKIFCOPY)
printf(" NPY_WRITEBACKIFCOPY");
printf("\n");
// 如果 ndarray 的基类不为 NULL 且是一个 PyArrayObject,则进行递归打印其基类的信息
if (fobj->base != NULL && PyArray_Check(fobj->base)) {
printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
printf("Dump of array's BASE:\n");
PyArray_DebugPrint((PyArrayObject *)fobj->base);
printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
}
// 打印结束的分隔线,并刷新输出缓冲区
printf("-------------------------------------------------------\n");
fflush(stdout);
}
/* Call this from contexts where an array might be written to, but we have no
* way to tell. (E.g., when converting to a read-write buffer.)
*/
// 当可能写入数组但没有明确方法告知时调用此函数(例如,在转换为读写缓冲区时)
NPY_NO_EXPORT int
array_might_be_written(PyArrayObject *obj)
{
// 提示消息的内容,用于警告可能写入的情况
const char *msg =
"Numpy has detected that you (may be) writing to an array with\n"
"overlapping memory from np.broadcast_arrays. If this is intentional\n"
"set the WRITEABLE flag True or make a copy immediately before writing.";
// 如果 obj 的标志中包含 NPY_ARRAY_WARN_ON_WRITE
if (PyArray_FLAGS(obj) & NPY_ARRAY_WARN_ON_WRITE) {
// 弃用警告消息,并在失败时返回 -1
if (DEPRECATE(msg) < 0) {
return -1;
}
// 一次性只发出一次警告
while (1) {
// 清除数组的 WARN_ON_WRITE 标志
PyArray_CLEARFLAGS(obj, NPY_ARRAY_WARN_ON_WRITE);
// 如果 obj 的基类不为空且是一个 PyArrayObject,则继续处理其基类
if (!PyArray_BASE(obj) || !PyArray_Check(PyArray_BASE(obj))) {
break;
}
obj = (PyArrayObject *)PyArray_BASE(obj);
}
}
return 0;
}
/*
* NUMPY_API
*
* 如果 *obj* 是可写的,则此函数不执行任何操作并返回 0。
* 如果 *obj* 不可写,则引发异常并返回 -1。
* 它可能还会执行其他的一些工作,例如在数组转变为视图时发出警告。
* 在写入数组之前一定要调用此函数。
*
* *name* 是数组的名称,用于提供更好的错误消息。
* 可以是诸如 "assignment destination"、"output array",甚至只是 "array"。
*/
NPY_NO_EXPORT int
PyArray_FailUnlessWriteable(PyArrayObject *obj, const char *name)
{
// 检查 *obj* 是否为可写数组,如果不可写则设置异常并返回 -1
if (!PyArray_ISWRITEABLE(obj)) {
PyErr_Format(PyExc_ValueError, "%s is read-only", name);
return -1;
}
// 检查数组是否可能被写入,如果是则返回 -1
if (array_might_be_written(obj) < 0) {
return -1;
}
// 如果 *obj* 可写,返回 0
return 0;
}
/*
* 从 umath/string_ufuncs.cpp/h 中引入
*/
NPY_NO_EXPORT PyObject *
_umath_strings_richcompare(
PyArrayObject *self, PyArrayObject *other, int cmp_op, int rstrip);
/*
* VOID 类型的数组只能进行相等和不相等比较
* 在这种情况下,所有字段都会被提取并逐个进行测试...
* 相等性测试通过对所有字段使用逻辑与进行。
* 不等性测试通过对所有字段使用逻辑或进行。
*
* 没有字段的 VOID 类型数组通过直接比较每个位置上的内存进行相等性比较。
*/
static PyObject *
_void_compare(PyArrayObject *self, PyArrayObject *other, int cmp_op)
{
// 检查是否支持除了相等和不相等之外的比较操作,不支持则设置异常并返回 NULL
if (!(cmp_op == Py_EQ || cmp_op == Py_NE)) {
PyErr_SetString(PyExc_TypeError,
"Void-arrays can only be compared for equality.");
return NULL;
}
// 检查 *other* 是否为 NPY_VOID 类型,不是则设置异常并返回 NULL
if (PyArray_TYPE(other) != NPY_VOID) {
PyErr_SetString(PyExc_TypeError,
"Cannot compare structured or void to non-void arrays.");
return NULL;
}
// 如果 *self* 或 *other* 具有字段,则设置异常并返回 NULL
else if (PyArray_HASFIELDS(self) || PyArray_HASFIELDS(other)) {
PyErr_SetString(PyExc_TypeError,
"Cannot compare structured with unstructured void arrays. "
"(unreachable error, please report to NumPy devs.)");
return NULL;
}
else {
/*
* 由于数组吸收了子数组描述符,只有在两个数组都具有非结构化 VOID "V<len>" 数据类型时才会进入此路径。
*/
// 检查 *self* 和 *other* 的 ITEMSIZE 是否相同,如果不同则设置异常并返回 NULL
if (PyArray_ITEMSIZE(self) != PyArray_ITEMSIZE(other)) {
PyErr_SetString(PyExc_TypeError,
"cannot compare unstructured voids of different length. "
"Use bytes to compare. "
"(This may return array of False in the future.)");
return NULL;
}
/* 使用字符串比较。假设 *self* 和 *other* 具有相同的 descr->type */
// 调用 _umath_strings_richcompare 函数进行字符串比较,并返回结果
return _umath_strings_richcompare(self, other, cmp_op, 0);
}
}
/*
* Silence the current error and emit a deprecation warning instead.
*
* If warnings are raised as errors, this sets the warning __cause__ to the
* silenced error.
*/
NPY_NO_EXPORT int
DEPRECATE_silence_error(const char *msg) {
// 声明保存异常信息的对象指针
PyObject *exc, *val, *tb;
// 捕获当前异常
PyErr_Fetch(&exc, &val, &tb);
// 发出过时警告信息,并检查是否发生错误
if (DEPRECATE(msg) < 0) {
// 如果发生错误,将之前捕获的异常设置为警告信息的 __cause__
npy_PyErr_ChainExceptionsCause(exc, val, tb);
return -1;
}
// 清除异常对象的引用计数
Py_XDECREF(exc);
Py_XDECREF(val);
Py_XDECREF(tb);
return 0;
}
NPY_NO_EXPORT PyObject *
array_richcompare(PyArrayObject *self, PyObject *other, int cmp_op)
{
// 声明其他用于比较的数组对象和结果对象
PyArrayObject *array_other;
PyObject *obj_self = (PyObject *)self;
PyObject *result = NULL;
switch (cmp_op) {
case Py_LT:
// 如果需要,放弃富比较
RICHCMP_GIVE_UP_IF_NEEDED(obj_self, other);
// 执行小于比较操作
result = PyArray_GenericBinaryFunction(
(PyObject *)self, other, n_ops.less);
break;
case Py_LE:
// 如果需要,放弃富比较
RICHCMP_GIVE_UP_IF_NEEDED(obj_self, other);
// 执行小于等于比较操作
result = PyArray_GenericBinaryFunction(
(PyObject *)self, other, n_ops.less_equal);
break;
case Py_EQ:
// 如果需要,放弃富比较
RICHCMP_GIVE_UP_IF_NEEDED(obj_self, other);
/*
* The ufunc does not support void/structured types, so these
* need to be handled specifically. Only a few cases are supported.
*/
// 如果数组类型为 NPY_VOID
if (PyArray_TYPE(self) == NPY_VOID) {
// 将 Python 对象 other 转换为 PyArrayObject 类型
array_other = (PyArrayObject *)PyArray_FROM_O(other);
// 如果转换不成功,发出过时警告并返回 NotImplemented
if (array_other == NULL) {
/* 2015-05-07, 1.10 */
if (DEPRECATE_silence_error(
"elementwise == comparison failed and returning scalar "
"instead; this will raise an error in the future.") < 0) {
return NULL;
}
// 增加 NotImplemented 对象的引用计数并返回
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
// 对 VOID 类型进行比较操作
result = _void_compare(self, array_other, cmp_op);
Py_DECREF(array_other);
return result;
}
// 执行一般的相等比较操作
result = PyArray_GenericBinaryFunction(
(PyObject *)self, (PyObject *)other, n_ops.equal);
break;
case Py_NE:
RICHCMP_GIVE_UP_IF_NEEDED(obj_self, other);
"""
* 该 ufunc 不支持 void/structured 类型,因此需要特别处理。
* 仅支持少数情况。
"""
if (PyArray_TYPE(self) == NPY_VOID) {
array_other = (PyArrayObject *)PyArray_FROM_O(other);
"""
* 如果转换不成功,则表示无法以这种方式比较项目。
"""
if (array_other == NULL) {
/* 2015-05-07, 1.10 */
if (DEPRECATE_silence_error(
"elementwise != comparison failed and returning scalar "
"instead; this will raise an error in the future.") < 0) {
return NULL;
}
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
result = _void_compare(self, array_other, cmp_op);
Py_DECREF(array_other);
return result;
}
result = PyArray_GenericBinaryFunction(
(PyObject *)self, (PyObject *)other, n_ops.not_equal);
break;
case Py_GT:
RICHCMP_GIVE_UP_IF_NEEDED(obj_self, other);
result = PyArray_GenericBinaryFunction(
(PyObject *)self, other, n_ops.greater);
break;
case Py_GE:
RICHCMP_GIVE_UP_IF_NEEDED(obj_self, other);
result = PyArray_GenericBinaryFunction(
(PyObject *)self, other, n_ops.greater_equal);
break;
default:
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
/*
* 现在,`self` 可以通过将 `other` 转换为数组来控制操作
* (它有机会接管操作)。
* 如果我们不在 `==` 和 `!=` 中,这将是一个错误,
* 我们希望现有的错误能够有意义,并且源自 `TypeError`
* (当 Python 应该为 `NotImplemented` 抛出时)。
*
* 然而,如果问题是给定 dtypes 没有匹配的循环,
* 并且我们在 `==` 和 `!=` 中,那么返回一个 True 或 False 的数组是有意义的
* (遵循 Python 对于 `==` 和 `!=` 的行为)。
* 实际上:两个 *dtypes* 告诉我们它们无法比较。
*
* 理论上,错误可能会在对象循环内部引发,解决这个问题的方法可能是将其推入到 ufunc 中
* (在那里我们可以轻松区分这两种情况)。
* 实际上,这似乎不应该是一个巨大的问题:
* ufunc 循环本身将调用 `==`,它可能永远不会引发 UFuncNoLoopError。
*
* TODO: 如果/一旦我们正确地将结构化比较推入 ufunc,
* 我们可以考虑将这条路径作为回退循环推入 ufunc 本身
* (忽略输入数组)。
* 这将有一个优势,即实现 `__array_ufunc__` 的子类不需要显式地实现 `__eq__` 和 `__ne__`。
*/
if (result == NULL
&& (cmp_op == Py_EQ || cmp_op == Py_NE)
&& PyErr_ExceptionMatches(
npy_static_pydata._UFuncNoLoopError)) {
PyErr_Clear();
PyArrayObject *array_other = (PyArrayObject *)PyArray_FROM_O(other);
if (PyArray_TYPE(array_other) == NPY_VOID) {
/*
* 对于空值数组(void arrays),ufunc 目前不处理,所以如果 `other` 是空值数组,
* 我们会将操作推迟给它(会引发 TypeError)。
*/
Py_DECREF(array_other);
Py_RETURN_NOTIMPLEMENTED;
}
if (PyArray_NDIM(self) == 0 && PyArray_NDIM(array_other) == 0) {
/*
* (seberg)我不确定这是否最佳做法,但是我们暂时通过返回 `NotImplemented` 来保留
* “标量”输入的 Python 布尔结果。
*/
Py_DECREF(array_other);
Py_RETURN_NOTIMPLEMENTED;
}
/* Hack warning: using NpyIter to allocate broadcasted result. */
PyArrayObject *ops[3] = {self, array_other, NULL};
npy_uint32 flags = NPY_ITER_ZEROSIZE_OK | NPY_ITER_REFS_OK;
npy_uint32 op_flags[3] = {
NPY_ITER_READONLY, NPY_ITER_READONLY,
NPY_ITER_ALLOCATE | NPY_ITER_WRITEONLY};
PyArray_Descr *bool_descr = PyArray_DescrFromType(NPY_BOOL);
PyArray_Descr *op_descrs[3] = {
PyArray_DESCR(self), PyArray_DESCR(array_other), bool_descr};
// 使用 NpyIter_MultiNew 创建多重迭代器,用于处理多个操作数
NpyIter *iter = NpyIter_MultiNew(
3, ops, flags, NPY_KEEPORDER, NPY_NO_CASTING,
op_flags, op_descrs);
Py_CLEAR(bool_descr);
Py_CLEAR(array_other);
if (iter == NULL) {
return NULL;
}
// 从迭代器中获取结果数组,这里是第三个操作数
PyArrayObject *res = NpyIter_GetOperandArray(iter)[2];
Py_INCREF(res);
if (NpyIter_Deallocate(iter) != NPY_SUCCEED) {
Py_DECREF(res);
return NULL;
}
/*
* 数组保证是新分配的,因此是连续的,可以简单地用 0 或 1 填充它。
*/
memset(PyArray_BYTES(res), cmp_op == Py_EQ ? 0 : 1, PyArray_NBYTES(res));
/* Ensure basic subclass support by wrapping: */
if (!PyArray_CheckExact(self)) {
/*
* 如果 `other` 也是一个子类(优先级更高),我们已经推迟处理了。所以使用 `self` 进行包装。
* 如果用户需要更多,他们需要重写 `==` 和 `!=`。
*/
PyObject *wrapped = npy_apply_wrap_simple(self, res);
Py_DECREF(res);
return wrapped;
}
return (PyObject *)res;
}
return result;
}
/*NUMPY_API
*/
NPY_NO_EXPORT int
PyArray_ElementStrides(PyObject *obj)
{
PyArrayObject *arr; // 定义一个 PyArrayObject 指针变量 arr
int itemsize; // 定义一个整型变量 itemsize
int i, ndim; // 定义两个整型变量 i 和 ndim
npy_intp *strides; // 定义一个 npy_intp 类型的指针变量 strides
// 如果 obj 不是一个 NumPy 数组对象,则返回 0
if (!PyArray_Check(obj)) {
return 0;
}
arr = (PyArrayObject *)obj; // 将 obj 强制转换为 PyArrayObject 类型的指针并赋值给 arr
itemsize = PyArray_ITEMSIZE(arr); // 调用 PyArray_ITEMSIZE 宏,获取 arr 对象的每个元素大小
ndim = PyArray_NDIM(arr); // 调用 PyArray_NDIM 宏,获取 arr 对象的维度数
strides = PyArray_STRIDES(arr); // 调用 PyArray_STRIDES 宏,获取 arr 对象的步长数组
// 遍历数组的每一个维度
for (i = 0; i < ndim; i++) {
// 检查当前维度的步长是否是 itemsize 的整数倍,如果不是则返回 0
if ((strides[i] % itemsize) != 0) {
return 0;
}
}
return 1; // 如果所有维度的步长都符合条件,则返回 1
}
/*
* This routine checks to see if newstrides (of length nd) will not
* ever be able to walk outside of the memory implied numbytes and offset.
*
* The available memory is assumed to start at -offset and proceed
* to numbytes-offset. The strides are checked to ensure
* that accessing memory using striding will not try to reach beyond
* this memory for any of the axes.
*
* If numbytes is 0 it will be calculated using the dimensions and
* element-size.
*
* This function checks for walking beyond the beginning and right-end
* of the buffer and therefore works for any integer stride (positive
* or negative).
*/
/*NUMPY_API*/
NPY_NO_EXPORT npy_bool
PyArray_CheckStrides(int elsize, int nd, npy_intp numbytes, npy_intp offset,
npy_intp const *dims, npy_intp const *newstrides)
{
npy_intp begin, end; // 定义两个 npy_intp 类型的变量 begin 和 end
npy_intp lower_offset; // 定义一个 npy_intp 类型的变量 lower_offset
npy_intp upper_offset; // 定义一个 npy_intp 类型的变量 upper_offset
// 如果 numbytes 为 0,则根据维度和元素大小计算 numbytes
if (numbytes == 0) {
numbytes = PyArray_MultiplyList(dims, nd) * elsize;
}
begin = -offset; // 计算可用内存的起始位置
end = numbytes - offset; // 计算可用内存的结束位置
// 调用 offset_bounds_from_strides 函数,计算根据新步长数组 newstrides 计算的下限和上限偏移量
offset_bounds_from_strides(elsize, nd, dims, newstrides,
&lower_offset, &upper_offset);
// 检查上限偏移量是否超出了可用内存的结束位置,或者下限偏移量是否超出了可用内存的起始位置
if ((upper_offset > end) || (lower_offset < begin)) {
return NPY_FALSE; // 如果超出范围则返回 NPY_FALSE
}
return NPY_TRUE; // 如果在范围内则返回 NPY_TRUE
}
static PyObject *
array_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)
{
static char *kwlist[] = {"shape", "dtype", "buffer", "offset", "strides",
"order", NULL};
PyArray_Descr *descr = NULL; // 定义一个 PyArray_Descr 指针变量 descr
int itemsize; // 定义一个整型变量 itemsize
PyArray_Dims dims = {NULL, 0}; // 定义一个 PyArray_Dims 结构体变量 dims,初始化为 NULL 和 0
PyArray_Dims strides = {NULL, -1}; // 定义一个 PyArray_Dims 结构体变量 strides,初始化为 NULL 和 -1
PyArray_Chunk buffer; // 定义一个 PyArray_Chunk 结构体变量 buffer
npy_longlong offset = 0; // 定义一个 npy_longlong 类型的变量 offset,初始化为 0
NPY_ORDER order = NPY_CORDER; // 定义一个 NPY_ORDER 类型的变量 order,初始化为 NPY_CORDER
int is_f_order = 0; // 定义一个整型变量 is_f_order,初始化为 0
PyArrayObject *ret; // 定义一个 PyArrayObject 指针变量 ret
buffer.ptr = NULL; // 将 buffer.ptr 成员变量设置为 NULL
/*
* Usually called with shape and type but can also be called with buffer,
* strides, and swapped info For now, let's just use this to create an
* empty, contiguous array of a specific type and shape.
*/
# 使用 PyArg_ParseTupleAndKeywords 解析传入的参数,格式为 "O&|O&O&LO&O&:ndarray",
# 对应的解析器分别为 PyArray_IntpConverter, PyArray_DescrConverter,
# PyArray_BufferConverter, PyArray_OptionalIntpConverter, PyArray_OrderConverter。
# 如果解析失败,则跳转到失败处理块。
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O&|O&O&LO&O&:ndarray",
kwlist, PyArray_IntpConverter,
&dims,
PyArray_DescrConverter,
&descr,
PyArray_BufferConverter,
&buffer,
&offset,
&PyArray_OptionalIntpConverter,
&strides,
&PyArray_OrderConverter,
&order)) {
goto fail;
}
# 如果数组的存储顺序是 NPY_FORTRANORDER,则设置为列优先存储。
if (order == NPY_FORTRANORDER) {
is_f_order = 1;
}
# 如果没有提供数组的描述符,则使用默认类型创建一个描述符。
if (descr == NULL) {
descr = PyArray_DescrFromType(NPY_DEFAULT_TYPE);
}
# 获取数组元素的大小。
itemsize = descr->elsize;
# 如果 strides.len 不等于 -1,表示用户提供了步幅信息。
if (strides.len != -1) {
npy_intp nb, off;
# 如果步幅的长度不等于维度的长度,则抛出值错误并跳转到失败处理块。
if (strides.len != dims.len) {
PyErr_SetString(PyExc_ValueError,
"strides, if given, must be " \
"the same length as shape");
goto fail;
}
# 如果缓冲区的指针为 NULL,则将 nb 和 off 设置为 0。
# 否则,分别使用 buffer.len 和 offset 赋值给 nb 和 off。
if (buffer.ptr == NULL) {
nb = 0;
off = 0;
}
else {
nb = buffer.len;
off = (npy_intp) offset;
}
# 检查给定的步幅是否与请求数组的形状和缓冲区大小兼容,
# 如果不兼容,则抛出值错误并跳转到失败处理块。
if (!PyArray_CheckStrides(itemsize, dims.len,
nb, off,
dims.ptr, strides.ptr)) {
PyErr_SetString(PyExc_ValueError,
"strides is incompatible " \
"with shape of requested " \
"array and size of buffer");
goto fail;
}
}
# 如果缓冲区的指针为 NULL,则创建一个新的数组对象,
# 使用提供的描述符和其他参数。
if (buffer.ptr == NULL) {
ret = (PyArrayObject *)
PyArray_NewFromDescr_int(subtype, descr,
(int)dims.len,
dims.ptr,
strides.ptr, NULL, is_f_order, NULL, NULL,
_NPY_ARRAY_ALLOW_EMPTY_STRING);
# 如果创建失败,则将描述符设置为 NULL,并跳转到失败处理块。
if (ret == NULL) {
descr = NULL;
goto fail;
}
# 如果数组描述符有参考检查的需求,
# 则将对象位置设置为 Py_None。
if (PyDataType_REFCHK(PyArray_DESCR(ret))) {
if (PyArray_SetObjectsToNone(ret) < 0) {
descr = NULL;
goto fail;
}
}
}
else {
/* 如果给定了缓冲区,则使用它 */
// 如果维度长度为1且第一个元素为-1,则根据缓冲区长度和每个元素大小计算第一个维度的大小
if (dims.len == 1 && dims.ptr[0] == -1) {
dims.ptr[0] = (buffer.len-(npy_intp)offset) / itemsize;
}
// 如果未指定步长并且缓冲区长度小于所需数组的总大小,则抛出类型错误异常
else if ((strides.ptr == NULL) &&
(buffer.len < (offset + (((npy_intp)itemsize)*
PyArray_MultiplyList(dims.ptr,
dims.len))))) {
PyErr_SetString(PyExc_TypeError,
"buffer is too small for " \
"requested array");
goto fail;
}
/* 获取可写和对齐的数组 */
// 如果数组应按F顺序存储,则设置数组的标志以指示F连续性
if (is_f_order) {
buffer.flags |= NPY_ARRAY_F_CONTIGUOUS;
}
// 从描述符创建新的数组对象,返回PyArrayObject指针
ret = (PyArrayObject *)PyArray_NewFromDescr_int(
subtype, descr,
dims.len, dims.ptr, strides.ptr, offset + (char *)buffer.ptr,
buffer.flags, NULL, buffer.base,
_NPY_ARRAY_ALLOW_EMPTY_STRING);
// 如果创建失败,则清空描述符并跳转到失败处理标签
if (ret == NULL) {
descr = NULL;
goto fail;
}
}
// 释放维度对象的缓存
npy_free_cache_dim_obj(dims);
// 释放步长对象的缓存
npy_free_cache_dim_obj(strides);
// 返回成功创建的数组对象的PyObject指针
return (PyObject *)ret;
fail:
// 清空描述符对象,释放维度和步长对象的缓存,返回NULL指针表示创建数组失败
Py_XDECREF(descr);
npy_free_cache_dim_obj(dims);
npy_free_cache_dim_obj(strides);
return NULL;
# 定义静态全局变量 PyArray_Type,表示 numpy.ndarray 类型对象
NPY_NO_EXPORT PyTypeObject PyArray_Type = {
# 使用 PyVarObject_HEAD_INIT 宏初始化变量头部信息
PyVarObject_HEAD_INIT(NULL, 0)
# 设置类型名称为 "numpy.ndarray"
.tp_name = "numpy.ndarray",
# 设置对象基本大小为 PyArrayObject_fields 结构体的大小
.tp_basicsize = sizeof(PyArrayObject_fields),
# 方法定义部分
# 定义对象销毁时的函数为 array_dealloc
.tp_dealloc = (destructor)array_dealloc,
# 定义对象的字符串表示方法为 array_repr
.tp_repr = (reprfunc)array_repr,
# 定义对象作为数字的接口为 array_as_number
.tp_as_number = &array_as_number,
# 定义对象作为序列的接口为 array_as_sequence
.tp_as_sequence = &array_as_sequence,
# 定义对象作为映射的接口为 array_as_mapping
.tp_as_mapping = &array_as_mapping,
# 定义对象的字符串转换方法为 array_str
.tp_str = (reprfunc)array_str,
# 定义对象作为缓冲区的接口为 array_as_buffer
.tp_as_buffer = &array_as_buffer,
# 设置对象的默认标志为 Py_TPFLAGS_DEFAULT 和 Py_TPFLAGS_BASETYPE 的按位或
.tp_flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE),
# 定义对象的富比较函数为 array_richcompare
.tp_richcompare = (richcmpfunc)array_richcompare,
# 设置弱引用列表的偏移量为 PyArrayObject_fields 结构体中 weakreflist 的偏移量
.tp_weaklistoffset = offsetof(PyArrayObject_fields, weakreflist),
# 定义对象的迭代器函数为 array_iter
.tp_iter = (getiterfunc)array_iter,
# 定义对象的方法集合为 array_methods
.tp_methods = array_methods,
# 定义对象的属性获取与设置列表为 array_getsetlist
.tp_getset = array_getsetlist,
# 定义对象的构造函数为 array_new
.tp_new = (newfunc)array_new,
};
.\numpy\numpy\_core\src\multiarray\arrayobject.h
extern "C" {
// 声明一个函数 _strings_richcompare,它接受两个 PyArrayObject 对象和两个整数参数,返回一个 PyObject 对象
NPY_NO_EXPORT PyObject *
_strings_richcompare(PyArrayObject *self, PyArrayObject *other, int cmp_op,
int rstrip);
// 声明一个函数 array_richcompare,它接受一个 PyArrayObject 对象和一个 PyObject 对象以及一个整数参数,返回一个 PyObject 对象
NPY_NO_EXPORT PyObject *
array_richcompare(PyArrayObject *self, PyObject *other, int cmp_op);
// 声明一个函数 array_might_be_written,它接受一个 PyArrayObject 对象,返回一个整数值
NPY_NO_EXPORT int
array_might_be_written(PyArrayObject *obj);
/*
* 定义一个常量 NPY_ARRAY_WARN_ON_WRITE,表示一个标志位,用于标记我们希望在将来转换为视图的数组。
* 当第一次尝试写入数组时会发出警告(但允许写入成功)。
* 这个标志仅供内部使用,可能会在将来的版本中移除,因此不会暴露给用户代码。
*/
static const int NPY_ARRAY_WARN_ON_WRITE = (1 << 31);
/*
* 下面三个标志用于内部表示曾经是 Python 标量(int, float, complex)的数组。
* 这些标志只能在本地上下文中使用,当数组不被返回时。
* 使用三个标志是为了避免在使用标志时需要双重检查实际的 dtype。
*/
static const int NPY_ARRAY_WAS_PYTHON_INT = (1 << 30);
static const int NPY_ARRAY_WAS_PYTHON_FLOAT = (1 << 29);
static const int NPY_ARRAY_WAS_PYTHON_COMPLEX = (1 << 28);
/*
* 标记曾经是一个巨大整数,后来转换为对象数组(或无符号/非默认整数数组),但随后由临时数组替换。
* 这个标志仅在 ufunc 机制中使用,其中正确覆盖所有类型解析路径是棘手的。
*/
static const int NPY_ARRAY_WAS_INT_AND_REPLACED = (1 << 27);
static const int NPY_ARRAY_WAS_PYTHON_LITERAL = (1 << 30 | 1 << 29 | 1 << 28);
/*
* 此标志允许相同类型的强制转换,类似于 NPY_ARRAY_FORCECAST。
* 数组从不设置此标志;它们仅用作各种 FromAny 函数的参数标志。
*/
static const int NPY_ARRAY_SAME_KIND_CASTING = (1 << 26);
}
.\numpy\numpy\_core\src\multiarray\arraywrap.c
/*
* Definitions for dealing with array-wrap or array-prepare.
*/
/*
* Find the array wrap or array prepare method that applies to the inputs.
* outputs should NOT be passed, as they are considered individually while
* applying the wrapping.
*
* @param nin number of inputs
* @param inputs Original input objects
* @param out_wrap Set to the python callable or None (on success).
* @param out_wrap_type Set to the type belonging to the wrapper.
*/
NPY_NO_EXPORT int
npy_find_array_wrap(
int nin, PyObject *const *inputs,
PyObject **out_wrap, PyObject **out_wrap_type)
{
PyObject *wrap = NULL;
PyObject *wrap_type = NULL;
double priority = 0; /* silence uninitialized warning */
/*
* Iterate through all inputs taking the first one with an __array_wrap__
* and replace it if a later one has a higher priority.
* (Currently even priority=-inf can be picked if it is the only argument.)
*/
for (int i = 0; i < nin; i++) {
PyObject *obj = inputs[i];
if (PyArray_CheckExact(obj)) {
if (wrap == NULL || priority < NPY_PRIORITY) {
Py_INCREF(Py_None);
Py_XSETREF(wrap, Py_None);
priority = 0;
}
}
else if (PyArray_IsAnyScalar(obj)) {
if (wrap == NULL || priority < NPY_SCALAR_PRIORITY) {
Py_INCREF(Py_None);
Py_XSETREF(wrap, Py_None);
priority = NPY_SCALAR_PRIORITY;
}
}
else {
PyObject *new_wrap = PyArray_LookupSpecial_OnInstance(obj, npy_interned_str.array_wrap);
if (new_wrap == NULL) {
if (PyErr_Occurred()) {
goto fail;
}
continue;
}
double curr_priority = PyArray_GetPriority(obj, 0);
if (wrap == NULL || priority < curr_priority
/* Prefer subclasses `__array_wrap__`: */
|| (curr_priority == 0 && wrap == Py_None)) {
Py_XSETREF(wrap, new_wrap);
Py_INCREF(Py_TYPE(obj));
Py_XSETREF(wrap_type, (PyObject *)Py_TYPE(obj));
priority = curr_priority;
}
else {
Py_DECREF(new_wrap);
}
}
}
if (wrap == NULL) {
Py_INCREF(Py_None);
wrap = Py_None;
}
if (wrap_type == NULL) {
Py_INCREF(&PyArray_Type);
wrap_type = (PyObject *)&PyArray_Type;
}
*out_wrap = wrap;
*out_wrap_type = wrap_type;
return 0;
fail:
Py_XDECREF(wrap);
Py_XDECREF(wrap_type);
return -1;
}
/* 获取用于传递给 __array_wrap__ 方法中 context 参数的参数元组。
*
* 仅当至少一个参数不为 None 时,才会传递输出参数。
*/
static PyObject *
_get_wrap_prepare_args(NpyUFuncContext *context) {
// 如果 context 中的 out 为 NULL,增加输入参数的引用计数并返回
if (context->out == NULL) {
Py_INCREF(context->in);
return context->in;
}
else {
// 否则将输入参数和输出参数连接起来并返回
return PySequence_Concat(context->in, context->out);
}
}
/*
* 对结果数组应用数组包装。
*
* @param obj 需要包装的对象(应该是一个数组)。
* @param original_out NULL/None(两者都有效)或者其包装方法始终被使用/优先的对象。
* 命名方式是因为对于 `out=` 参数,我们总是触发其自己的包装方法。
* @param wrap 要调用的数组包装函数。
* @param wrap_type 包装函数所属的类型,在匹配时可以进行快速包装。
* @param context ufunc 的上下文或者 NULL,在普通 ufunc 调用中使用。
* @param return_scalar 是否优先返回标量。当传递 `original_out` 时忽略,因为 `out` 参数从不是标量。
* @param force_wrap 如果为 True,我们总是调用包装(对于子类),因为 ufunc 可能已经改变了内容。
*/
NPY_NO_EXPORT PyObject *
npy_apply_wrap(
PyObject *obj, PyObject *original_out,
PyObject *wrap, PyObject *wrap_type,
NpyUFuncContext *context, npy_bool return_scalar, npy_bool force_wrap)
{
PyObject *res = NULL;
PyObject *new_wrap = NULL;
PyArrayObject *arr = NULL;
PyObject *err_type, *err_value, *traceback;
/* 如果提供了 original_out,并且不是 NULL,则优先使用实际的 out 对象进行包装: */
if (original_out != NULL && original_out != Py_None) {
/*
* 如果传递了原始输出对象,则包装不应更改它。特别是,将其转换为标量没有意义。
* 因此,替换传入的 wrap 和 wrap_type。
*/
return_scalar = NPY_FALSE;
if (PyArray_CheckExact(original_out)) {
/* 替换传入的 wrap/wrap_type(借用的引用)为默认值。 */
wrap = Py_None;
wrap_type = (PyObject *)&PyArray_Type;
}
else {
/* 替换传入的 wrap/wrap_type(借用的引用)为 new_wrap/type。 */
new_wrap = PyArray_LookupSpecial_OnInstance(
original_out, npy_interned_str.array_wrap);
if (new_wrap != NULL) {
wrap = new_wrap;
wrap_type = (PyObject *)Py_TYPE(original_out);
}
else if (PyErr_Occurred()) {
return NULL;
}
}
}
/*
* 如果结果与包装类型相同(并且没有 original_out,当我们应该包装 `self` 时)
* 我们可以跳过包装,除非我们需要标量返回。
*/
/*
* 如果不返回标量并且不强制包装,并且对象的类型与wrap_type相同,
* 则直接返回对象,增加其引用计数。
*/
if (!return_scalar && !force_wrap
&& (PyObject *)Py_TYPE(obj) == wrap_type) {
Py_XDECREF(new_wrap); // 释放 new_wrap 的引用
Py_INCREF(obj); // 增加对象的引用计数
return obj; // 返回对象
}
/*
* 如果 wrap 是 Py_None,则释放 new_wrap 的引用,增加对象的引用计数。
* 如果 return_scalar 为真,则使用 PyArray_Return 转换为标量。
*/
if (wrap == Py_None) {
Py_XDECREF(new_wrap); // 释放 new_wrap 的引用
Py_INCREF(obj); // 增加对象的引用计数
if (return_scalar) {
/*
* 当需要时使用 PyArray_Return 将对象转换为标量
* (PyArray_Return 实际上检查非数组情况)。
*/
return PyArray_Return((PyArrayObject *)obj);
}
else {
return obj; // 返回对象
}
}
/*
* 需要调用 array-wrap 函数。在某些分支中,输入可能是非数组。
* (尽管我们应该尝试逐步淘汰所有这些分支!)
*/
PyObject *py_context = NULL;
if (context == NULL) {
Py_INCREF(Py_None); // 增加对 Py_None 的引用计数
py_context = Py_None; // 将 py_context 设置为 Py_None
}
else {
/* 使用适当的上下文调用方法 */
PyObject *args_tup = _get_wrap_prepare_args(context); // 获取准备参数的元组
if (args_tup == NULL) {
goto finish; // 如果获取参数元组失败,则跳转到 finish 标签
}
py_context = Py_BuildValue("OOi",
context->ufunc, args_tup, context->out_i); // 创建 py_context
Py_DECREF(args_tup); // 释放参数元组的引用
if (py_context == NULL) {
goto finish; // 如果创建 py_context 失败,则跳转到 finish 标签
}
}
if (PyArray_Check(obj)) {
Py_INCREF(obj); // 增加对象的引用计数
arr = (PyArrayObject *)obj; // 将 obj 转换为 PyArrayObject 类型
}
else {
/*
* TODO: 理想情况下,我们永远不会进入此分支!
* 但是当我们从 Python 使用时,NumPy 将 0 维数组转换为标量,
* 这意味着在 Python 中将其转换回数组可能不需要包装,这可能会出现问题。
*/
arr = (PyArrayObject *)PyArray_FromAny(obj, NULL, 0, 0, 0, NULL); // 尝试将 obj 转换为 PyArrayObject
if (arr == NULL) {
goto finish; // 如果转换失败,则跳转到 finish 标签
}
}
res = PyObject_CallFunctionObjArgs(
wrap, arr, py_context,
(return_scalar && PyArray_NDIM(arr) == 0) ? Py_True : Py_False,
NULL); // 调用 wrap 函数
if (res != NULL) {
goto finish; // 如果调用成功,则跳转到 finish 标签
}
else if (!PyErr_ExceptionMatches(PyExc_TypeError)) {
goto finish; // 如果异常不是 TypeError,则跳转到 finish 标签
}
/*
* 在不传递 return_scalar 参数的情况下重试。如果成功,则发出 DeprecationWarning。
* 当 context 为 None 时,无需尝试这样做。
*/
if (py_context != Py_None) {
PyErr_Fetch(&err_type, &err_value, &traceback); // 获取当前的错误信息
res = PyObject_CallFunctionObjArgs(wrap, arr, py_context, NULL); // 再次调用 wrap 函数
if (res != NULL) {
goto deprecation_warning; // 如果调用成功,则跳转到 deprecation_warning 标签
}
Py_DECREF(err_type); // 释放错误类型的引用
Py_XDECREF(err_value); // 释放错误值的引用
Py_XDECREF(traceback); // 释放 traceback 的引用
if (!PyErr_ExceptionMatches(PyExc_TypeError)) {
goto finish; // 如果异常不是 TypeError,则跳转到 finish 标签
}
}
/*
* 在不传递 context 和 return_scalar 参数的情况下重试。
* 如果成功,则发出 DeprecationWarning。
*/
PyErr_Fetch(&err_type, &err_value, &traceback); // 获取当前的错误信息
res = PyObject_CallFunctionObjArgs(wrap, arr, NULL); // 再次调用 wrap 函数
if (res == NULL) {
// 清理错误类型对象的引用计数
Py_DECREF(err_type);
// 清理错误值对象的引用计数
Py_XDECREF(err_value);
// 清理 traceback 对象的引用计数
Py_XDECREF(traceback);
// 跳转到函数结束标签
goto finish;
}
deprecation_warning:
/* 如果程序执行到这里,说明原始的错误仍然被保留。 */
/* 在2024年1月17日被弃用,NumPy 2.0 */
// 发出弃用警告,警告未来版本中 __array_wrap__ 必须接受 context 和 return_scalar 参数(位置参数)。
// (在NumPy 2.0版本弃用)
if (DEPRECATE(
"__array_wrap__ must accept context and return_scalar arguments "
"(positionally) in the future. (Deprecated NumPy 2.0)") < 0) {
// 将当前错误链接到先前的错误链中
npy_PyErr_ChainExceptionsCause(err_type, err_value, traceback);
// 清理结果对象的引用计数
Py_CLEAR(res);
}
else {
// 清理错误类型对象的引用计数
Py_DECREF(err_type);
// 清理错误值对象的引用计数
Py_XDECREF(err_value);
// 清理 traceback 对象的引用计数
Py_XDECREF(traceback);
}
finish:
// 清理 py_context 对象的引用计数
Py_XDECREF(py_context);
// 清理 arr 对象的引用计数
Py_XDECREF(arr);
// 清理 new_wrap 对象的引用计数
Py_XDECREF(new_wrap);
// 返回结果对象
return res;
/*
* 调用 arr_of_subclass 的 __array_wrap__(towrap) 方法,
* 使 'towrap' 具有与 'arr_of_subclass' 相同的 ndarray 子类。
* `towrap` 应为一个基类 ndarray。
*/
NPY_NO_EXPORT PyObject *
npy_apply_wrap_simple(PyArrayObject *arr_of_subclass, PyArrayObject *towrap)
{
/*
* 当只有单个其他数组时,与 apply-wrap 相同,
* 我们可以使用 `original_out`,而不必担心传递一个有用的 wrap 对象。
*/
return npy_apply_wrap(
(PyObject *)towrap, (PyObject *)arr_of_subclass, Py_None, NULL,
NULL, NPY_FALSE, NPY_TRUE);
}
.\numpy\numpy\_core\src\multiarray\arraywrap.h
// 定义结构体 NpyUFuncContext,用于保存 ufunc 对象及其输入输出参数
typedef struct {
PyObject *ufunc; // ufunc 对象
PyObject *in; // 输入对象
PyObject *out; // 输出对象
int out_i; // 输出索引
} NpyUFuncContext;
// 不导出的函数声明:将 obj 对象应用于 wrap 包装并返回结果
NPY_NO_EXPORT PyObject *
npy_apply_wrap(
PyObject *obj, PyObject *original_out,
PyObject *wrap, PyObject *wrap_type,
NpyUFuncContext *context, npy_bool return_scalar, npy_bool force_wrap);
// 不导出的函数声明:将 arr_of_subclass 数组对象应用于 towrap 包装并返回结果
NPY_NO_EXPORT PyObject *
npy_apply_wrap_simple(PyArrayObject *arr_of_subclass, PyArrayObject *towrap);
// 不导出的函数声明:寻找数组的包装器并设置输出包装器和包装类型
NPY_NO_EXPORT int
npy_find_array_wrap(
int nin, PyObject *const *inputs,
PyObject **out_wrap, PyObject **out_wrap_type);
注释:
- `
- `
- `typedef struct { ... } NpyUFuncContext;`:定义了一个结构体 `NpyUFuncContext`,用于存储 ufunc 对象及其输入输出参数。
- `NPY_NO_EXPORT PyObject * npy_apply_wrap(...)`:不导出的函数声明,将给定的对象应用于包装器并返回结果。
- `NPY_NO_EXPORT PyObject * npy_apply_wrap_simple(...)`:不导出的函数声明,对给定的数组对象应用简单的包装器并返回结果。
- `NPY_NO_EXPORT int npy_find_array_wrap(...)`:不导出的函数声明,寻找数组的包装器并设置输出包装器和包装类型。
- `
.\numpy\numpy\_core\src\multiarray\array_assign_array.c
/*
* This file implements assignment from an ndarray to another ndarray.
*
* Written by Mark Wiebe (mwwiebe@gmail.com)
* Copyright (c) 2011 by Enthought, Inc.
*
* See LICENSE.txt for the license.
*/
/*
* Check that array data is both uint-aligned and true-aligned for all array
* elements, as required by the copy/casting code in lowlevel_strided_loops.c
*/
NPY_NO_EXPORT int
copycast_isaligned(int ndim, npy_intp const *shape,
PyArray_Descr *dtype, char *data, npy_intp const *strides)
{
int aligned;
int big_aln, small_aln;
int uint_aln = npy_uint_alignment(dtype->elsize); // Calculate uint alignment requirement
int true_aln = dtype->alignment; // Get true alignment requirement
/* uint alignment can be 0, meaning not uint alignable */
if (uint_aln == 0) {
return 0; // Return false if uint alignment is zero
}
/*
* As an optimization, it is unnecessary to check the alignment to the
* smaller of (uint_aln, true_aln) if the data is aligned to the bigger of
* the two and the big is a multiple of the small aln. We check the bigger
* one first and only check the smaller if necessary.
*/
if (true_aln >= uint_aln) {
big_aln = true_aln;
small_aln = uint_aln;
}
else {
big_aln = uint_aln;
small_aln = true_aln;
}
// Check alignment of array data based on the bigger alignment requirement
aligned = raw_array_is_aligned(ndim, shape, data, strides, big_aln);
if (aligned && big_aln % small_aln != 0) {
aligned = raw_array_is_aligned(ndim, shape, data, strides, small_aln);
}
return aligned; // Return whether data is aligned
}
/*
* Assigns the array from 'src' to 'dst'. The strides must already have
* been broadcast.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
raw_array_assign_array(int ndim, npy_intp const *shape,
PyArray_Descr *dst_dtype, char *dst_data, npy_intp const *dst_strides,
PyArray_Descr *src_dtype, char *src_data, npy_intp const *src_strides)
{
int idim;
npy_intp shape_it[NPY_MAXDIMS];
npy_intp dst_strides_it[NPY_MAXDIMS];
npy_intp src_strides_it[NPY_MAXDIMS];
npy_intp coord[NPY_MAXDIMS];
int aligned;
NPY_BEGIN_THREADS_DEF; // Macro to begin thread-safe operations
// Check if both source and destination arrays are aligned
aligned =
copycast_isaligned(ndim, shape, dst_dtype, dst_data, dst_strides) &&
copycast_isaligned(ndim, shape, src_dtype, src_data, src_strides);
/* Use raw iteration with no heap allocation */
// 调用 NumPy 函数准备两个原始数组的迭代器,以进行类型转换和数据拷贝
if (PyArray_PrepareTwoRawArrayIter(
ndim, shape,
dst_data, dst_strides,
src_data, src_strides,
&ndim, shape_it,
&dst_data, dst_strides_it,
&src_data, src_strides_it) < 0) {
// 如果准备迭代器失败,则返回错误码
return -1;
}
/*
* 检查在一维情况下的重叠情况。更高维度的数组和相反的步长会在此之前进行临时拷贝。
*/
if (ndim == 1 && src_data < dst_data &&
src_data + shape_it[0] * src_strides_it[0] > dst_data) {
// 如果存在重叠,调整源数据和目标数据的位置和步长
src_data += (shape_it[0] - 1) * src_strides_it[0];
dst_data += (shape_it[0] - 1) * dst_strides_it[0];
src_strides_it[0] = -src_strides_it[0];
dst_strides_it[0] = -dst_strides_it[0];
}
/* 获取执行类型转换的函数 */
NPY_cast_info cast_info;
NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetDTypeTransferFunction(aligned,
src_strides_it[0], dst_strides_it[0],
src_dtype, dst_dtype,
0,
&cast_info, &flags) != NPY_SUCCEED) {
// 如果获取类型转换函数失败,则返回错误码
return -1;
}
// 清除浮点错误状态标志,如果标志位中不包含 NPY_METH_NO_FLOATINGPOINT_ERRORS
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
npy_clear_floatstatus_barrier((char*)&src_data);
}
/* 确保元素数量超过线程化的阈值 */
if (!(flags & NPY_METH_REQUIRES_PYAPI)) {
// 计算需要处理的总元素个数
npy_intp nitems = 1, i;
for (i = 0; i < ndim; i++) {
nitems *= shape_it[i];
}
// 在超过阈值时启用线程化处理
NPY_BEGIN_THREADS_THRESHOLDED(nitems);
}
// 创建步长数组
npy_intp strides[2] = {src_strides_it[0], dst_strides_it[0]};
// 开始迭代处理数组数据
NPY_RAW_ITER_START(idim, ndim, coord, shape_it) {
/* 处理最内层的维度 */
char *args[2] = {src_data, dst_data};
// 调用类型转换函数处理当前迭代位置的数据
if (cast_info.func(&cast_info.context,
args, &shape_it[0], strides, cast_info.auxdata) < 0) {
// 如果处理失败,则跳转到失败处理标签
goto fail;
}
} NPY_RAW_ITER_TWO_NEXT(idim, ndim, coord, shape_it,
dst_data, dst_strides_it,
src_data, src_strides_it);
// 结束多线程处理
NPY_END_THREADS;
// 释放类型转换信息结构体
NPY_cast_info_xfree(&cast_info);
// 如果标志位中不包含 NPY_METH_NO_FLOATINGPOINT_ERRORS
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
// 获取浮点错误状态,并在需要时处理浮点错误
int fpes = npy_get_floatstatus_barrier((char*)&src_data);
if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
// 处理浮点错误失败时返回错误码
return -1;
}
}
// 处理成功,返回 0 表示没有错误
return 0;
/*
* 终止多线程执行,用于异常情况下的清理工作
*/
fail:
NPY_END_THREADS;
/*
* 释放指向 cast_info 的内存
*/
NPY_cast_info_xfree(&cast_info);
/*
* 返回 -1 表示函数执行失败
*/
return -1;
}
/*
* 将 'src' 数组根据 'wheremask' 数组中的 True 值分配给 'dst' 数组。
* 要求 'dst' 和 'src' 的步长已经广播过。
*
* 成功时返回 0,失败时返回 -1。
*/
NPY_NO_EXPORT int
raw_array_wheremasked_assign_array(int ndim, npy_intp const *shape,
PyArray_Descr *dst_dtype, char *dst_data, npy_intp const *dst_strides,
PyArray_Descr *src_dtype, char *src_data, npy_intp const *src_strides,
PyArray_Descr *wheremask_dtype, char *wheremask_data,
npy_intp const *wheremask_strides)
{
int idim;
npy_intp shape_it[NPY_MAXDIMS];
npy_intp dst_strides_it[NPY_MAXDIMS];
npy_intp src_strides_it[NPY_MAXDIMS];
npy_intp wheremask_strides_it[NPY_MAXDIMS];
npy_intp coord[NPY_MAXDIMS];
int aligned;
/*
* 定义多线程开始的宏,用于线程安全操作
*/
NPY_BEGIN_THREADS_DEF;
/*
* 检查 'dst' 和 'src' 是否按字节对齐,并进行类型转换
*/
aligned =
copycast_isaligned(ndim, shape, dst_dtype, dst_data, dst_strides) &&
copycast_isaligned(ndim, shape, src_dtype, src_data, src_strides);
/*
* 使用原始迭代,无堆内存分配
*/
if (PyArray_PrepareThreeRawArrayIter(
ndim, shape,
dst_data, dst_strides,
src_data, src_strides,
wheremask_data, wheremask_strides,
&ndim, shape_it,
&dst_data, dst_strides_it,
&src_data, src_strides_it,
&wheremask_data, wheremask_strides_it) < 0) {
return -1;
}
/*
* 检查是否有重叠,对于一维情况进行处理,高维数组在此之前会进行临时复制
*/
if (ndim == 1 && src_data < dst_data &&
src_data + shape_it[0] * src_strides_it[0] > dst_data) {
src_data += (shape_it[0] - 1) * src_strides_it[0];
dst_data += (shape_it[0] - 1) * dst_strides_it[0];
wheremask_data += (shape_it[0] - 1) * wheremask_strides_it[0];
src_strides_it[0] = -src_strides_it[0];
dst_strides_it[0] = -dst_strides_it[0];
wheremask_strides_it[0] = -wheremask_strides_it[0];
}
/*
* 获取执行类型转换的函数信息
*/
NPY_cast_info cast_info;
NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetMaskedDTypeTransferFunction(aligned,
src_strides_it[0],
dst_strides_it[0],
wheremask_strides_it[0],
src_dtype, dst_dtype, wheremask_dtype,
0,
&cast_info, &flags) != NPY_SUCCEED) {
return -1;
}
/*
* 清除浮点状态异常,如果转换不需要 Python API
*/
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
npy_clear_floatstatus_barrier(src_data);
}
/*
* 如果转换不需要 Python API,根据数据项数目启动线程
*/
if (!(flags & NPY_METH_REQUIRES_PYAPI)) {
npy_intp nitems = 1, i;
for (i = 0; i < ndim; i++) {
nitems *= shape_it[i];
}
NPY_BEGIN_THREADS_THRESHOLDED(nitems);
}
/*
* 设置步长数组,用于转换函数
*/
npy_intp strides[2] = {src_strides_it[0], dst_strides_it[0]};
NPY_RAW_ITER_START(idim, ndim, coord, shape_it) {
PyArray_MaskedStridedUnaryOp *stransfer;
stransfer = (PyArray_MaskedStridedUnaryOp *)cast_info.func;
/* 处理最内层的维度 */
char *args[2] = {src_data, dst_data};
if (stransfer(&cast_info.context,
args, &shape_it[0], strides,
(npy_bool *)wheremask_data, wheremask_strides_it[0],
cast_info.auxdata) < 0) {
goto fail;
}
} NPY_RAW_ITER_THREE_NEXT(idim, ndim, coord, shape_it,
dst_data, dst_strides_it,
src_data, src_strides_it,
wheremask_data, wheremask_strides_it);
NPY_END_THREADS;
NPY_cast_info_xfree(&cast_info);
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
int fpes = npy_get_floatstatus_barrier(src_data);
if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
return -1;
}
}
return 0;
fail:
// 终止多线程执行
NPY_END_THREADS;
// 释放分配的转换信息内存
NPY_cast_info_xfree(&cast_info);
// 返回错误码
return -1;
}
/*
* An array assignment function for copying arrays, broadcasting 'src' into
* 'dst'. This function makes a temporary copy of 'src' if 'src' and
* 'dst' overlap, to be able to handle views of the same data with
* different strides.
*
* dst: The destination array.
* src: The source array.
* wheremask: If non-NULL, a boolean mask specifying where to copy.
* casting: An exception is raised if the copy violates this
* casting rule.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
PyArray_AssignArray(PyArrayObject *dst, PyArrayObject *src,
PyArrayObject *wheremask,
NPY_CASTING casting)
{
// 是否复制了源数组
int copied_src = 0;
// 源数组的步长
npy_intp src_strides[NPY_MAXDIMS];
/* Use array_assign_scalar if 'src' NDIM is 0 */
// 如果源数组的维度为0,则使用标量赋值函数
if (PyArray_NDIM(src) == 0) {
return PyArray_AssignRawScalar(
dst, PyArray_DESCR(src), PyArray_DATA(src),
wheremask, casting);
}
/*
* Performance fix for expressions like "a[1000:6000] += x". In this
* case, first an in-place add is done, followed by an assignment,
* equivalently expressed like this:
*
* tmp = a[1000:6000]
* np.add(tmp, x, tmp)
* a[1000:6000] = tmp
*
* In the assignment the underlying data type, shape, strides, and
* data pointers are identical, but src != dst because they are separately
* generated slices. By detecting this and skipping the redundant
* copy of values to themselves, we potentially give a big speed boost.
*
* Note that we don't call EquivTypes, because usually the exact same
* dtype object will appear, and we don't want to slow things down
* with a complicated comparison. The comparisons are ordered to
* try and reject this with as little work as possible.
*/
// 检测是否可以跳过冗余的数组复制操作以提升性能
if (PyArray_DATA(src) == PyArray_DATA(dst) &&
PyArray_DESCR(src) == PyArray_DESCR(dst) &&
PyArray_NDIM(src) == PyArray_NDIM(dst) &&
PyArray_CompareLists(PyArray_DIMS(src),
PyArray_DIMS(dst),
PyArray_NDIM(src)) &&
PyArray_CompareLists(PyArray_STRIDES(src),
PyArray_STRIDES(dst),
PyArray_NDIM(src))) {
/*printf("Redundant copy operation detected\n");*/
// 如果数据类型、形状、步长和数据指针都相同,则跳过冗余复制操作
return 0;
}
// 检查目标数组是否可写
if (PyArray_FailUnlessWriteable(dst, "assignment destination") < 0) {
// 如果不可写,则跳转到失败标签
goto fail;
}
/* Check the casting rule */
/*
* 如果不能将源数组src的数据类型安全地转换为目标数组dst的数据类型,报错并跳转到fail标签处。
*/
if (!PyArray_CanCastTypeTo(PyArray_DESCR(src),
PyArray_DESCR(dst), casting)) {
npy_set_invalid_cast_error(
PyArray_DESCR(src), PyArray_DESCR(dst), casting, NPY_FALSE);
goto fail;
}
/*
* 当目标数组dst的维度为1,并且步长指向同一个方向时,内部的最低级循环处理重叠数据的复制。
* 对于更高维度和步长相反的一维数据,如果src和dst重叠,我们会为src创建一个临时副本。
*/
if (((PyArray_NDIM(dst) == 1 && PyArray_NDIM(src) >= 1 &&
PyArray_STRIDES(dst)[0] *
PyArray_STRIDES(src)[PyArray_NDIM(src) - 1] < 0) ||
PyArray_NDIM(dst) > 1 || PyArray_HASFIELDS(dst)) &&
arrays_overlap(src, dst)) {
PyArrayObject *tmp;
/*
* 分配一个临时的复制数组。
*/
tmp = (PyArrayObject *)PyArray_NewLikeArray(dst,
NPY_KEEPORDER, NULL, 0);
if (tmp == NULL) {
goto fail;
}
if (PyArray_AssignArray(tmp, src, NULL, NPY_UNSAFE_CASTING) < 0) {
Py_DECREF(tmp);
goto fail;
}
src = tmp;
copied_src = 1;
}
/* 将src广播到dst以进行原始迭代 */
if (PyArray_NDIM(src) > PyArray_NDIM(dst)) {
int ndim_tmp = PyArray_NDIM(src);
npy_intp *src_shape_tmp = PyArray_DIMS(src);
npy_intp *src_strides_tmp = PyArray_STRIDES(src);
/*
* 作为向后兼容的特例,从src左侧去掉单位维度。
*/
while (ndim_tmp > PyArray_NDIM(dst) && src_shape_tmp[0] == 1) {
--ndim_tmp;
++src_shape_tmp;
++src_strides_tmp;
}
if (broadcast_strides(PyArray_NDIM(dst), PyArray_DIMS(dst),
ndim_tmp, src_shape_tmp,
src_strides_tmp, "input array",
src_strides) < 0) {
goto fail;
}
}
else {
if (broadcast_strides(PyArray_NDIM(dst), PyArray_DIMS(dst),
PyArray_NDIM(src), PyArray_DIMS(src),
PyArray_STRIDES(src), "input array",
src_strides) < 0) {
goto fail;
}
}
/* 优化:标量布尔掩码 */
if (wheremask != NULL &&
PyArray_NDIM(wheremask) == 0 &&
PyArray_DESCR(wheremask)->type_num == NPY_BOOL) {
npy_bool value = *(npy_bool *)PyArray_DATA(wheremask);
if (value) {
/* 当where=True时,相当于不使用任何where条件 */
wheremask = NULL;
}
else {
/* 当where=False时,不复制任何数据 */
return 0;
}
}
if (wheremask == NULL) {
/* 直接赋值操作 */
/* 使用原始数组迭代进行赋值 */
if (raw_array_assign_array(PyArray_NDIM(dst), PyArray_DIMS(dst),
PyArray_DESCR(dst), PyArray_DATA(dst), PyArray_STRIDES(dst),
PyArray_DESCR(src), PyArray_DATA(src), src_strides) < 0) {
goto fail;
}
}
else {
npy_intp wheremask_strides[NPY_MAXDIMS];
/* 将 wheremask 广播到 'dst' 以便进行原始迭代 */
if (broadcast_strides(PyArray_NDIM(dst), PyArray_DIMS(dst),
PyArray_NDIM(wheremask), PyArray_DIMS(wheremask),
PyArray_STRIDES(wheremask), "where mask",
wheremask_strides) < 0) {
goto fail;
}
/* 带有 where-mask 的直接赋值操作 */
/* 使用带有 where-mask 的原始数组迭代进行赋值 */
if (raw_array_wheremasked_assign_array(
PyArray_NDIM(dst), PyArray_DIMS(dst),
PyArray_DESCR(dst), PyArray_DATA(dst), PyArray_STRIDES(dst),
PyArray_DESCR(src), PyArray_DATA(src), src_strides,
PyArray_DESCR(wheremask), PyArray_DATA(wheremask),
wheremask_strides) < 0) {
goto fail;
}
}
if (copied_src) {
Py_DECREF(src);
}
return 0;
fail:
// 如果已经成功复制了源对象,则需要减少其引用计数
if (copied_src) {
Py_DECREF(src);
}
// 返回-1表示函数执行失败
return -1;
}
.\numpy\numpy\_core\src\multiarray\array_assign_scalar.c
/*
* This file implements assignment from a scalar to an ndarray.
*
* Written by Mark Wiebe (mwwiebe@gmail.com)
* Copyright (c) 2011 by Enthought, Inc.
*
* See LICENSE.txt for the license.
*/
/*
* Assigns the scalar value to every element of the destination raw array.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
raw_array_assign_scalar(int ndim, npy_intp const *shape,
PyArray_Descr *dst_dtype, char *dst_data, npy_intp const *dst_strides,
PyArray_Descr *src_dtype, char *src_data)
{
int idim;
npy_intp shape_it[NPY_MAXDIMS], dst_strides_it[NPY_MAXDIMS];
npy_intp coord[NPY_MAXDIMS];
int aligned;
NPY_BEGIN_THREADS_DEF;
/* Check both uint and true alignment */
aligned = raw_array_is_aligned(ndim, shape, dst_data, dst_strides,
npy_uint_alignment(dst_dtype->elsize)) &&
raw_array_is_aligned(ndim, shape, dst_data, dst_strides,
dst_dtype->alignment) &&
npy_is_aligned(src_data, npy_uint_alignment(src_dtype->elsize)) &&
npy_is_aligned(src_data, src_dtype->alignment);
/* Use raw iteration with no heap allocation */
if (PyArray_PrepareOneRawArrayIter(
ndim, shape,
dst_data, dst_strides,
&ndim, shape_it,
&dst_data, dst_strides_it) < 0) {
return -1;
}
/* Get the function to do the casting */
NPY_cast_info cast_info;
NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetDTypeTransferFunction(aligned,
0, dst_strides_it[0],
src_dtype, dst_dtype,
0,
&cast_info, &flags) != NPY_SUCCEED) {
return -1;
}
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
npy_clear_floatstatus_barrier(src_data);
}
if (!(flags & NPY_METH_REQUIRES_PYAPI)) {
npy_intp nitems = 1, i;
for (i = 0; i < ndim; i++) {
nitems *= shape_it[i];
}
NPY_BEGIN_THREADS_THRESHOLDED(nitems);
}
npy_intp strides[2] = {0, dst_strides_it[0]};
NPY_RAW_ITER_START(idim, ndim, coord, shape_it) {
/* Process the innermost dimension */
char *args[2] = {src_data, dst_data};
if (cast_info.func(&cast_info.context,
args, &shape_it[0], strides, cast_info.auxdata) < 0) {
goto fail;
}
} NPY_RAW_ITER_ONE_NEXT(idim, ndim, coord,
shape_it, dst_data, dst_strides_it);
// 调用宏 `NPY_RAW_ITER_ONE_NEXT`,用于迭代并处理数据数组中的元素,执行下一个迭代步骤。
NPY_END_THREADS;
// 结束线程,用于在使用多线程执行过程中的清理工作。
NPY_cast_info_xfree(&cast_info);
// 释放用于类型转换的信息结构体 `cast_info` 的内存。
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
// 如果 `flags` 中未设置 `NPY_METH_NO_FLOATINGPOINT_ERRORS` 标志位,则执行以下操作,处理浮点数错误。
int fpes = npy_get_floatstatus_barrier(src_data);
// 获取源数据 `src_data` 中的浮点数状态,并在此处设置一个障碍,确保获取时是稳定的状态。
if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
// 如果浮点数状态 `fpes` 非零,并且调用 `PyUFunc_GiveFloatingpointErrors` 处理浮点数错误时返回小于零的错误码。
return -1;
}
}
// 如果未发生浮点数错误或处理浮点数错误时成功,继续执行。
return 0;
// 返回整数值 `0`,表示函数执行成功。
fail:
NPY_END_THREADS;
NPY_cast_info_xfree(&cast_info);
return -1;
}
/*
* Assigns the scalar value to every element of the destination raw array
* where the 'wheremask' value is True.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
raw_array_wheremasked_assign_scalar(int ndim, npy_intp const *shape,
PyArray_Descr *dst_dtype, char *dst_data, npy_intp const *dst_strides,
PyArray_Descr *src_dtype, char *src_data,
PyArray_Descr *wheremask_dtype, char *wheremask_data,
npy_intp const *wheremask_strides)
{
int idim;
npy_intp shape_it[NPY_MAXDIMS], dst_strides_it[NPY_MAXDIMS];
npy_intp wheremask_strides_it[NPY_MAXDIMS];
npy_intp coord[NPY_MAXDIMS];
int aligned;
NPY_BEGIN_THREADS_DEF;
/* Check both uint and true alignment */
aligned = raw_array_is_aligned(ndim, shape, dst_data, dst_strides,
npy_uint_alignment(dst_dtype->elsize)) &&
raw_array_is_aligned(ndim, shape, dst_data, dst_strides,
dst_dtype->alignment) &&
npy_is_aligned(src_data, npy_uint_alignment(src_dtype->elsize) &&
npy_is_aligned(src_data, src_dtype->alignment));
/* Use raw iteration with no heap allocation */
if (PyArray_PrepareTwoRawArrayIter(
ndim, shape,
dst_data, dst_strides,
wheremask_data, wheremask_strides,
&ndim, shape_it,
&dst_data, dst_strides_it,
&wheremask_data, wheremask_strides_it) < 0) {
return -1;
}
/* Get the function to do the casting */
NPY_cast_info cast_info;
NPY_ARRAYMETHOD_FLAGS flags;
if (PyArray_GetMaskedDTypeTransferFunction(aligned,
0, dst_strides_it[0], wheremask_strides_it[0],
src_dtype, dst_dtype, wheremask_dtype,
0,
&cast_info, &flags) != NPY_SUCCEED) {
return -1;
}
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
npy_clear_floatstatus_barrier(src_data);
}
if (!(flags & NPY_METH_REQUIRES_PYAPI)) {
npy_intp nitems = 1, i;
for (i = 0; i < ndim; i++) {
nitems *= shape_it[i];
}
NPY_BEGIN_THREADS_THRESHOLDED(nitems);
}
npy_intp strides[2] = {0, dst_strides_it[0]};
NPY_RAW_ITER_START(idim, ndim, coord, shape_it) {
/* Process the innermost dimension */
PyArray_MaskedStridedUnaryOp *stransfer;
stransfer = (PyArray_MaskedStridedUnaryOp *)cast_info.func;
char *args[2] = {src_data, dst_data};
if (stransfer(&cast_info.context,
args, &shape_it[0], strides,
(npy_bool *)wheremask_data, wheremask_strides_it[0],
cast_info.auxdata) < 0) {
goto fail;
}
/* Continue processing if successful */
}
NPY_END_THREADS;
return 0;
}
} NPY_RAW_ITER_TWO_NEXT(idim, ndim, coord, shape_it,
dst_data, dst_strides_it,
wheremask_data, wheremask_strides_it);
这行代码调用了一个宏 `NPY_RAW_ITER_TWO_NEXT`,它在本语境中可能是一个用于迭代的宏,接受多个参数,包括迭代器状态、数据指针和步长等。
NPY_END_THREADS;
该语句调用宏 `NPY_END_THREADS`,用于结束多线程操作。
NPY_cast_info_xfree(&cast_info);
调用函数 `NPY_cast_info_xfree`,释放 `cast_info` 结构体或对象所占用的内存空间。
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
条件判断语句,检查 `flags` 变量是否包含位掩码 `NPY_METH_NO_FLOATINGPOINT_ERRORS` 的反义。如果未包含该位掩码,则执行以下代码块。
int fpes = npy_get_floatstatus_barrier(src_data);
声明并初始化整数变量 `fpes`,调用函数 `npy_get_floatstatus_barrier` 来获取 `src_data` 的浮点数状态。
if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
return -1;
}
条件判断语句,如果 `fpes` 非零且调用 `PyUFunc_GiveFloatingpointErrors` 函数返回负值,返回 -1。
}
条件判断语句结束。
return 0;
函数返回整数值 0,表示成功执行。
/*
* NPY_END_THREADS;
* Ends any threading operations in NumPy's internal routines.
* This macro is typically used to ensure thread safety.
*/
fail:
NPY_END_THREADS;
/*
* NPY_cast_info_xfree(&cast_info);
* Frees the memory associated with the cast information structure.
* The cast_info structure holds details about type casting operations.
*/
NPY_cast_info_xfree(&cast_info);
/*
* return -1;
* Indicates failure by returning -1 to the caller of this function.
*/
return -1;
}
/*
* Assigns a scalar value specified by 'src_dtype' and 'src_data'
* to elements of 'dst'.
*
* dst: The destination array.
* src_dtype: The data type of the source scalar.
* src_data: The memory element of the source scalar.
* wheremask: If non-NULL, a boolean mask specifying where to copy.
* casting: An exception is raised if the assignment violates this
* casting rule.
*
* This function is implemented in array_assign_scalar.c.
*
* Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
PyArray_AssignRawScalar(PyArrayObject *dst,
PyArray_Descr *src_dtype, char *src_data,
PyArrayObject *wheremask,
NPY_CASTING casting)
{
/*
* int allocated_src_data = 0;
* Flag indicating if memory was allocated for src_data.
*/
int allocated_src_data = 0;
/*
* npy_longlong scalarbuffer[4];
* Buffer used to store the scalar value when a temporary buffer is needed.
* It's sized to accommodate up to 4 npy_longlong values.
*/
npy_longlong scalarbuffer[4];
/*
* if (PyArray_FailUnlessWriteable(dst, "assignment destination") < 0) {
* Checks if the destination array 'dst' is writable; raises an error if not.
*/
if (PyArray_FailUnlessWriteable(dst, "assignment destination") < 0) {
return -1;
}
/*
* Check the casting rule:
* if (!can_cast_scalar_to(src_dtype, src_data,
* PyArray_DESCR(dst), casting)) {
* Validates if casting of 'src_data' to 'dst' is permissible based on 'casting'.
*/
if (!can_cast_scalar_to(src_dtype, src_data,
PyArray_DESCR(dst), casting)) {
npy_set_invalid_cast_error(
src_dtype, PyArray_DESCR(dst), casting, NPY_TRUE);
return -1;
}
/*
* Make a copy of the src data if it's a different dtype than 'dst'
* or isn't aligned, and the destination we're copying to has
* more than one element. To avoid having to manage object lifetimes,
* we also skip this if 'dst' has an object dtype.
*/
if ((!PyArray_EquivTypes(PyArray_DESCR(dst), src_dtype) ||
!(npy_is_aligned(src_data, npy_uint_alignment(src_dtype->elsize)) &&
npy_is_aligned(src_data, src_dtype->alignment))) &&
PyArray_SIZE(dst) > 1 &&
!PyDataType_REFCHK(PyArray_DESCR(dst))) {
char *tmp_src_data;
/*
* Use a static buffer to store the aligned/cast version,
* or allocate some memory if more space is needed.
*/
if ((int)sizeof(scalarbuffer) >= PyArray_ITEMSIZE(dst)) {
tmp_src_data = (char *)&scalarbuffer[0];
}
else {
tmp_src_data = PyArray_malloc(PyArray_ITEMSIZE(dst));
if (tmp_src_data == NULL) {
PyErr_NoMemory();
goto fail;
}
allocated_src_data = 1;
}
/*
* if (PyDataType_FLAGCHK(PyArray_DESCR(dst), NPY_NEEDS_INIT)) {
* memset(tmp_src_data, 0, PyArray_ITEMSIZE(dst));
* }
* Initializes 'tmp_src_data' with zeros if 'dst' requires initialization.
*/
if (PyDataType_FLAGCHK(PyArray_DESCR(dst), NPY_NEEDS_INIT)) {
memset(tmp_src_data, 0, PyArray_ITEMSIZE(dst));
}
/*
* if (PyArray_CastRawArrays(1, src_data, tmp_src_data, 0, 0,
* src_dtype, PyArray_DESCR(dst), 0) != NPY_SUCCEED) {
* src_data = tmp_src_data;
* goto fail;
* }
* Casts 'src_data' to match the type of 'dst'; uses 'tmp_src_data' if necessary.
*/
if (PyArray_CastRawArrays(1, src_data, tmp_src_data, 0, 0,
src_dtype, PyArray_DESCR(dst), 0) != NPY_SUCCEED) {
src_data = tmp_src_data;
goto fail;
}
/*
* src_data = tmp_src_data;
* src_dtype = PyArray_DESCR(dst);
* Reassigns 'src_data' and 'src_dtype' to reflect the new data and type.
*/
src_data = tmp_src_data;
src_dtype = PyArray_DESCR(dst);
}
# 如果wheremask为NULL,则进行数值赋值
if (wheremask == NULL) {
# 使用原始数组迭代进行赋值
if (raw_array_assign_scalar(PyArray_NDIM(dst), PyArray_DIMS(dst),
PyArray_DESCR(dst), PyArray_DATA(dst), PyArray_STRIDES(dst),
src_dtype, src_data) < 0) {
goto fail;
}
}
# 如果wheremask不为NULL
else {
# 创建用于广播的wheremask_strides数组
npy_intp wheremask_strides[NPY_MAXDIMS];
# 将wheremask广播到'dst'进行原始迭代
if (broadcast_strides(PyArray_NDIM(dst), PyArray_DIMS(dst),
PyArray_NDIM(wheremask), PyArray_DIMS(wheremask),
PyArray_STRIDES(wheremask), "where mask",
wheremask_strides) < 0) {
goto fail;
}
# 使用原始数组迭代进行带掩码的赋值
if (raw_array_wheremasked_assign_scalar(
PyArray_NDIM(dst), PyArray_DIMS(dst),
PyArray_DESCR(dst), PyArray_DATA(dst), PyArray_STRIDES(dst),
src_dtype, src_data,
PyArray_DESCR(wheremask), PyArray_DATA(wheremask),
wheremask_strides) < 0) {
goto fail;
}
}
# 如果分配了src_data,则释放它
if (allocated_src_data) {
PyArray_free(src_data);
}
# 返回0表示成功
return 0;
# 如果已经分配了源数据(allocated_src_data 为真),则释放源数据内存
if (allocated_src_data) {
PyArray_free(src_data);
}
# 返回错误代码 -1,表示操作失败
return -1;
.\numpy\numpy\_core\src\multiarray\array_coercion.c
// 定义以避免使用废弃的 NumPy API 版本
// 定义用于标识 Multiarray 模块
// 定义用于标识 Umath 模块
// 清除 PY_SSIZE_T 的旧定义,确保只使用干净的定义
// 包含 Python.h 头文件,提供 Python C API 的功能
// 包含 NumPy 3k 兼容性工具头文件,提供与 Python 3k 兼容的功能
// 包含低级分步循环头文件,定义了低级的步进循环操作
// 包含 NumPy 数组对象头文件,提供了数组对象的定义和操作
// 包含 NumPy 数学函数头文件,提供了数学函数的声明和定义
// 包含描述符头文件,提供了数据类型描述符的定义
// 包含数据类型转换头文件,提供了数据类型转换的相关函数和结构体
// 包含数据类型元信息头文件,提供了数据类型元信息的定义
// 包含字符串数据类型头文件,提供了字符串数据类型的定义
// 包含 NumPy 参数解析头文件,提供了解析参数的函数和结构体
// 包含抽象数据类型头文件,提供了抽象数据类型的定义和操作
// 包含数组强制转换头文件,提供了数组类型之间强制转换的函数
// 包含构造函数头文件,提供了数组对象的构造函数定义
// 包含通用功能头文件,提供了一些通用的辅助函数和宏定义
// 包含日期时间头文件,提供了日期时间相关的函数和结构体
// 包含 NumPy 导入头文件,提供了导入 NumPy 模块的函数和宏定义
// 包含引用计数头文件,提供了引用计数相关的函数和宏定义
// 包含 Umath 模块的头文件,提供了 Umath 模块的函数和宏定义
/*
* This file defines helpers for some of the ctors.c functions which
* create an array from Python sequences and types.
* When creating an array with ``np.array(...)`` we have to do two main things:
*
* 1. Find the exact shape of the resulting array
* 2. Find the correct dtype of the resulting array.
*
* In most cases these two things are can be done in a single processing step.
* There are in principle three different calls that should be distinguished:
*
* 1. The user calls ``np.array(..., dtype=np.dtype("<f8"))``
* 2. The user calls ``np.array(..., dtype="S")``
* 3. The user calls ``np.array(...)``
*
* In the first case, in principle only the shape needs to be found. In the
* second case, the DType class (e.g. string) is already known but the DType
* instance (e.g. length of the string) has to be found.
* In the last case the DType class needs to be found as well. Note that
* it is not necessary to find the DType class of the entire array, but
* the DType class needs to be found for each element before the actual
* dtype instance can be found.
*
* Further, there are a few other things to keep in mind when coercing arrays:
*
* * For UFunc promotion, Python scalars need to be handled specially to
* allow value based casting. This requires python complex/float to
* have their own DTypes.
* * It is necessary to decide whether or not a sequence is an element.
* For example tuples are considered elements for structured dtypes, but
* otherwise are considered sequences.
* This means that if a dtype is given (either as a class or instance),
* it can effect the dimension discovery part.
* For the "special" NumPy types structured void and "c" (single character)
* this is special cased. For future user-types, this is currently
* handled by providing calling an `is_known_scalar` method. This method
* currently ensures that Python numerical types are handled quickly.
*
* In the initial version of this implementation, it is assumed that dtype
* discovery can be implemented sufficiently fast. That is, it is not
* necessary to create fast paths that only find the correct shape e.g. when
* ``dtype=np.dtype("f8")`` is given.
*
* The code here avoid multiple conversion of array-like objects (including
* sequences). These objects are cached after conversion, which will require
* additional memory, but can drastically speed up coercion from array like
* objects.
*/
/* 全局变量,用于存储 Python 类型到 DType 类型的映射字典 */
PyObject *_global_pytype_to_type_dict = NULL;
/* 枚举类型,用于在发现 dtype 和形状时跟踪或信号一些情况 */
enum _dtype_discovery_flags {
FOUND_RAGGED_ARRAY = 1 << 0, // 发现了不规则数组
GAVE_SUBCLASS_WARNING = 1 << 1, // 发出子类警告
PROMOTION_FAILED = 1 << 2, // 升级失败
DISCOVER_STRINGS_AS_SEQUENCES = 1 << 3, // 将字符串发现为序列
DISCOVER_TUPLES_AS_ELEMENTS = 1 << 4, // 将元组发现为元素
MAX_DIMS_WAS_REACHED = 1 << 5, // 达到最大维数
DESCRIPTOR_WAS_SET = 1 << 6, // 描述符已设置
COPY_WAS_CREATED_BY__ARRAY__ = 1 << 7, // 复制由__array__创建
};
/**
* 向全局类型字典中添加已知的序列类型,注意当传入 DType 时可能会忽略此查找。
*
* @return -1 表示错误,0 表示成功
*/
static int
_prime_global_pytype_to_type_dict(void)
{
int res;
/* 添加基本的 Python 序列类型 */
res = PyDict_SetItem(_global_pytype_to_type_dict,
(PyObject *)&PyList_Type, Py_None);
if (res < 0) {
return -1;
}
res = PyDict_SetItem(_global_pytype_to_type_dict,
(PyObject *)&PyTuple_Type, Py_None);
if (res < 0) {
return -1;
}
/* NumPy 数组不作为标量处理 */
res = PyDict_SetItem(_global_pytype_to_type_dict,
(PyObject *)&PyArray_Type, Py_None);
if (res < 0) {
return -1;
}
return 0;
}
/**
* 将一个新的映射从 Python 类型到 DType 类添加。对于用户定义的传统 dtype,
* 除非 pytype 是从 `np.generic` 继承的,否则此函数将不执行任何操作。
*
* 假设 DType 类确保持有 python 类型(这个假设是保证的)。
* 此功能取代了 ``_typenum_fromtypeobj``。
*
* @param DType 要映射到的 DType 类
* @param pytype 要映射的 Python 类型
* @param userdef 是否是用户定义的标量。我们确保用户定义的标量从我们的标量继承(目前)。
*/
NPY_NO_EXPORT int
_PyArray_MapPyTypeToDType(
PyArray_DTypeMeta *DType, PyTypeObject *pytype, npy_bool userdef)
{
PyObject *Dtype_obj = (PyObject *)DType;
if (userdef && !PyObject_IsSubclass(
(PyObject *)pytype, (PyObject *)&PyGenericArrType_Type)) {
/*
* 我们期望用户定义的 dtype(目前)将子类化某个 numpy 标量类,以允许自动发现。
*/
if (NPY_DT_is_legacy(DType)) {
/*
* 对于传统的用户定义 dtype,发现依赖于子类化,但支持任意类型对象,因此不执行任何操作。
*/
return 0;
}
/*
* 我们当前强制用户 DType 必须从 `np.generic` 继承
* (这应该成为 `np.generic` 基类,并可能被完全取消)。
*/
PyErr_Format(PyExc_RuntimeError,
"当前仅支持为从 `np.generic` 派生的标量注册 DType,得到 '%S'。",
(PyObject *)pytype);
return -1;
}
/* 如果全局字典 _global_pytype_to_type_dict 不存在,则创建 */
if (NPY_UNLIKELY(_global_pytype_to_type_dict == NULL)) {
_global_pytype_to_type_dict = PyDict_New();
if (_global_pytype_to_type_dict == NULL) {
// 创建失败,返回错误代码
return -1;
}
// 初始化全局字典 _global_pytype_to_type_dict
if (_prime_global_pytype_to_type_dict() < 0) {
// 初始化失败,返回错误代码
return -1;
}
}
// 检查字典中是否包含给定的 pytype 对象
int res = PyDict_Contains(_global_pytype_to_type_dict, (PyObject *)pytype);
if (res < 0) {
// 包含检查失败,返回错误代码
return -1;
}
else if (DType == &PyArray_StringDType) {
// 如果 DType 是 PyArray_StringDType,它的标量类型为 str,
// 我们允许它,因为它不参与 DType 推断,因此不添加到 pytype 到 type 映射中
return 0;
}
else if (res) {
// 如果字典中已经存在相同的 pytype 映射,抛出运行时错误
PyErr_SetString(PyExc_RuntimeError,
"Can only map one python type to DType.");
return -1;
}
// 将 Dtype_obj 添加到 _global_pytype_to_type_dict 中,关联到 pytype
return PyDict_SetItem(_global_pytype_to_type_dict,
(PyObject *)pytype, Dtype_obj);
/**
* Lookup the DType for a registered known python scalar type.
*
* @param pytype Python Type to look up
* @return DType, None if it is a known non-scalar, or NULL if an unknown object.
*/
static inline PyArray_DTypeMeta *
npy_discover_dtype_from_pytype(PyTypeObject *pytype)
{
PyObject *DType;
if (pytype == &PyArray_Type) {
DType = Py_None;
}
else if (pytype == &PyFloat_Type) {
DType = (PyObject *)&PyArray_PyFloatDType;
}
else if (pytype == &PyLong_Type) {
DType = (PyObject *)&PyArray_PyLongDType;
}
else {
DType = PyDict_GetItem(_global_pytype_to_type_dict,
(PyObject *)pytype);
if (DType == NULL) {
/* the python type is not known */
return NULL;
}
}
Py_INCREF(DType);
assert(DType == Py_None || PyObject_TypeCheck(DType, (PyTypeObject *)&PyArrayDTypeMeta_Type));
return (PyArray_DTypeMeta *)DType;
}
/*
* Note: This function never fails, but will return `NULL` for unknown scalars
* and `None` for known array-likes (e.g. tuple, list, ndarray).
*/
NPY_NO_EXPORT PyObject *
PyArray_DiscoverDTypeFromScalarType(PyTypeObject *pytype)
{
return (PyObject *)npy_discover_dtype_from_pytype(pytype);
}
/**
* Find the correct DType class for the given python type. If flags is NULL
* this is not used to discover a dtype, but only for conversion to an
* existing dtype. In that case the Python (not NumPy) scalar subclass
* checks are skipped.
*
* @param obj The python object, mainly type(pyobj) is used, the object
* is passed to reuse existing code at this time only.
* @param flags Flags used to know if warnings were already given. If
* flags is NULL, this is not
* @param fixed_DType if not NULL, will be checked first for whether or not
* it can/wants to handle the (possible) scalar value.
* @return New reference to either a DType class, Py_None, or NULL on error.
*/
static inline PyArray_DTypeMeta *
discover_dtype_from_pyobject(
PyObject *obj, enum _dtype_discovery_flags *flags,
PyArray_DTypeMeta *fixed_DType)
{
if (fixed_DType != NULL) {
/*
* Let the given DType handle the discovery. This is when the
* scalar-type matches exactly, or the DType signals that it can
* handle the scalar-type. (Even if it cannot handle here it may be
* asked to attempt to do so later, if no other matching DType exists.)
*/
if ((Py_TYPE(obj) == fixed_DType->scalar_type) ||
NPY_DT_CALL_is_known_scalar_type(fixed_DType, Py_TYPE(obj))) {
Py_INCREF(fixed_DType);
return fixed_DType;
}
}
PyArray_DTypeMeta *DType = npy_discover_dtype_from_pytype(Py_TYPE(obj));
if (DType != NULL) {
return DType;
}
/*
* 现在我们还没有找到清晰的映射,但主要是为了向后兼容性,我们必须进一步尝试
* 将输入解释为已知的标量类型之一。
*/
PyArray_Descr *legacy_descr;
// 检查对象是否为标量对象,如果是则获取其描述符
if (PyArray_IsScalar(obj, Generic)) {
legacy_descr = PyArray_DescrFromScalar(obj);
// 如果获取描述符失败,则返回空指针
if (legacy_descr == NULL) {
return NULL;
}
}
// 如果标志为空,则返回空类型对象
else if (flags == NULL) {
Py_INCREF(Py_None);
return (PyArray_DTypeMeta *)Py_None;
}
// 如果对象为字节串,则获取字节类型描述符
else if (PyBytes_Check(obj)) {
legacy_descr = PyArray_DescrFromType(NPY_BYTE);
}
// 如果对象为 Unicode 字符串,则获取 Unicode 类型描述符
else if (PyUnicode_Check(obj)) {
legacy_descr = PyArray_DescrFromType(NPY_UNICODE);
}
// 否则调用 _array_find_python_scalar_type 函数尝试寻找 Python 标量类型
else {
legacy_descr = _array_find_python_scalar_type(obj);
}
// 如果成功获取描述符,则返回描述符并增加引用计数
if (legacy_descr != NULL) {
DType = NPY_DTYPE(legacy_descr);
Py_INCREF(DType);
Py_DECREF(legacy_descr);
/* TODO: 启用关于子类处理的警告 */
// 如果未曾发出过子类警告,则发出警告并设置标志
if ((0) && !((*flags) & GAVE_SUBCLASS_WARNING)) {
if (DEPRECATE_FUTUREWARNING(
"in the future NumPy will not automatically find the "
"dtype for subclasses of scalars known to NumPy (i.e. "
"python types). Use the appropriate `dtype=...` to create "
"this array. This will use the `object` dtype or raise "
"an error in the future.") < 0) {
return NULL;
}
*flags |= GAVE_SUBCLASS_WARNING;
}
// 返回数据类型对象
return DType;
}
// 如果没有匹配的描述符,则返回空类型对象
Py_INCREF(Py_None);
return (PyArray_DTypeMeta *)Py_None;
/**
* Discover the correct descriptor from a known DType class and scalar.
* If the fixed DType can discover a dtype instance/descr all is fine,
* if it cannot and DType is used instead, a cast will have to be tried.
*
* @param fixed_DType A user provided fixed DType, can be NULL
* @param DType A discovered DType (by discover_dtype_from_pyobject);
* this can be identical to `fixed_DType`, if it obj is a
* known scalar. Can be `NULL` indicating no known type.
* @param obj The Python scalar object. At the time of calling this function
* it must be known that `obj` should represent a scalar.
*/
static inline PyArray_Descr *
find_scalar_descriptor(
PyArray_DTypeMeta *fixed_DType, PyArray_DTypeMeta *DType,
PyObject *obj)
{
PyArray_Descr *descr;
if (DType == NULL && fixed_DType == NULL) {
/* No known DType and no fixed one means we go to object. */
return PyArray_DescrFromType(NPY_OBJECT);
}
else if (DType == NULL) {
/*
* If no DType is known/found, give the fixed give one a second
* chance. This allows for example string, to call `str(obj)` to
* figure out the length for arbitrary objects.
*/
descr = NPY_DT_CALL_discover_descr_from_pyobject(fixed_DType, obj);
}
else {
descr = NPY_DT_CALL_discover_descr_from_pyobject(DType, obj);
}
if (descr == NULL) {
return NULL;
}
if (fixed_DType == NULL) {
return descr;
}
Py_SETREF(descr, PyArray_CastDescrToDType(descr, fixed_DType));
return descr;
}
/**
* Helper function for casting a raw value from one descriptor to another.
* This helper uses the normal casting machinery, but e.g. does not care about
* checking cast safety.
*
* @param from_descr Descriptor of the source data.
* @param from_item Pointer to the raw data to be casted.
* @param to_descr Descriptor of the target data.
* @param to_item Pointer to the location where the casted data will be stored.
* @return Returns 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
npy_cast_raw_scalar_item(
PyArray_Descr *from_descr, char *from_item,
PyArray_Descr *to_descr, char *to_item)
{
NPY_cast_info cast_info;
NPY_ARRAYMETHOD_FLAGS flags;
// Obtain the casting information and method flags for the data transfer.
if (PyArray_GetDTypeTransferFunction(
0, 0, 0, from_descr, to_descr, 0, &cast_info,
&flags) == NPY_FAIL) {
return -1;
}
// Clear any floating-point errors before casting if needed.
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
npy_clear_floatstatus_barrier(from_item);
}
// Set up arguments and perform the cast operation.
char *args[2] = {from_item, to_item};
const npy_intp strides[2] = {0, 0};
const npy_intp length = 1;
if (cast_info.func(&cast_info.context,
args, &length, strides, cast_info.auxdata) < 0) {
NPY_cast_info_xfree(&cast_info);
return -1;
}
NPY_cast_info_xfree(&cast_info);
// Check and handle floating-point errors after casting if necessary.
if (!(flags & NPY_METH_NO_FLOATINGPOINT_ERRORS)) {
int fpes = npy_get_floatstatus_barrier(to_item);
if (fpes && PyUFunc_GiveFloatingpointErrors("cast", fpes) < 0) {
return -1;
}
}
return 0;
}
/*
* Assign a single element in an array from a python value.
*
* The dtypes SETITEM should only be trusted to generally do the right
* thing if something is known to be a scalar *and* is of a python type known
* to the DType (which should include all basic Python math types), but in
* general a cast may be necessary.
* This function handles the cast, which is for example hit when assigning
* a float128 to complex128.
*
* TODO: This function probably needs to be passed an "owner" for the sake of
* future HPy (non CPython) support
*
* NOTE: We do support 0-D exact NumPy arrays correctly via casting here.
* There be dragons, because we must NOT support generic array-likes.
* The problem is that some (e.g. astropy's Quantity and our masked
* arrays) have divergent behaviour for `__array__` as opposed to
* `__float__`. And they rely on that.
* That is arguably bad as it limits the things that work seamlessly
* because `__float__`, etc. cannot even begin to cover all of casting.
* However, we have no choice. We simply CANNOT support array-likes
* here without finding a solution for this first.
* And the only plausible one I see currently, is expanding protocols
* in some form, either to indicate that we want a scalar or to indicate
* that we want the unsafe version that `__array__` currently gives
* for both objects.
*
* If we ever figure out how to expand this to other array-likes, care
* may need to be taken. `PyArray_FromAny`/`PyArray_AssignFromCache`
* uses this function but know if the input is an array, array-like,
* or scalar. Relaxing things here should be OK, but looks a bit
* like possible recursion, so it may make sense to make a "scalars only"
* version of this function.
*
* @param descr The descriptor describing the data type of the array.
* @param item Pointer to the location where the value should be assigned.
* @param value Python object representing the value to be assigned.
* @return 0 on success, -1 on failure.
*/
NPY_NO_EXPORT int
PyArray_Pack(PyArray_Descr *descr, void *item, PyObject *value)
{
/* Initialize fields for the PyArrayObject */
PyArrayObject_fields arr_fields = {
.flags = NPY_ARRAY_WRITEABLE, /* assume array is not behaved. */
};
/* Set the type of the PyArrayObject to PyArray_Type */
Py_SET_TYPE(&arr_fields, &PyArray_Type);
/* Set the reference count of the PyArrayObject to 1 */
Py_SET_REFCNT(&arr_fields, 1);
/* Check if the dtype is NPY_OBJECT */
if (NPY_UNLIKELY(descr->type_num == NPY_OBJECT)) {
/*
* For object dtype, store objects directly; casting may lose type information.
* For other dtypes, discard the type information.
* TODO: This path might be necessary for Categorical[object].
*/
arr_fields.descr = descr;
return PyDataType_GetArrFuncs(descr)->setitem(value, item, &arr_fields);
}
/*
* Call discover_dtype_from_pyobject to determine the dtype meta for the value.
* This includes a check for is_known_scalar_type.
*/
PyArray_DTypeMeta *DType = discover_dtype_from_pyobject(
value, NULL, NPY_DTYPE(descr));
if (DType == NULL) {
return -1;
}
if (DType == (PyArray_DTypeMeta *)Py_None && PyArray_CheckExact(value)
&& PyArray_NDIM((PyArrayObject *)value) == 0) {
/*
* 警告:不要放宽上述 `PyArray_CheckExact` 的条件,除非你
* 仔细阅读了函数文档中的注意事项并理解了它。
*
* 注意:`ndim == 0` 的检查可能应该是一个错误,但是不幸的是。
* `arr.__float__()` 对于一个元素的数组有效,因此在某些情况下,
* 我们需要像处理标量一样处理它。
* (如果我们成功弃用上述功能,我们可以这样做。)
*/
Py_DECREF(DType);
PyArrayObject *arr = (PyArrayObject *)value;
if (PyArray_DESCR(arr) == descr && !PyDataType_REFCHK(descr)) {
/* 当描述符显然匹配时的轻量级快速路径 */
memcpy(item, PyArray_BYTES(arr), descr->elsize);
return 0; /* 成功(它是类似数组的结构) */
}
return npy_cast_raw_scalar_item(
PyArray_DESCR(arr), PyArray_BYTES(arr), descr, item);
}
if (DType == NPY_DTYPE(descr) || DType == (PyArray_DTypeMeta *)Py_None) {
/* 我们可以直接设置元素(或至少尝试) */
Py_XDECREF(DType);
arr_fields.descr = descr;
return PyDataType_GetArrFuncs(descr)->setitem(value, item, &arr_fields);
}
PyArray_Descr *tmp_descr;
tmp_descr = NPY_DT_CALL_discover_descr_from_pyobject(DType, value);
Py_DECREF(DType);
if (tmp_descr == NULL) {
return -1;
}
char *data = PyObject_Malloc(tmp_descr->elsize);
if (data == NULL) {
PyErr_NoMemory();
Py_DECREF(tmp_descr);
return -1;
}
if (PyDataType_FLAGCHK(tmp_descr, NPY_NEEDS_INIT)) {
memset(data, 0, tmp_descr->elsize);
}
arr_fields.descr = tmp_descr;
if (PyDataType_GetArrFuncs(tmp_descr)->setitem(value, data, &arr_fields) < 0) {
PyObject_Free(data);
Py_DECREF(tmp_descr);
return -1;
}
int res = npy_cast_raw_scalar_item(tmp_descr, data, descr, item);
if (PyDataType_REFCHK(tmp_descr)) {
if (PyArray_ClearBuffer(tmp_descr, data, 0, 1, 1) < 0) {
res = -1;
}
}
PyObject_Free(data);
Py_DECREF(tmp_descr);
return res;
}
/*
* 更新数组形状信息。
*
* @param curr_ndim 当前数组的维度
* @param max_ndim 允许的最大维度
* @param out_shape 输出数组的形状
* @param new_ndim 新增加的维度数
* @param new_shape 新的形状
* @param sequence 是否为序列
* @param flags 数据类型发现标志位
* @return 成功与否的标志,如果数组不规则返回-1
*/
static int
update_shape(int curr_ndim, int *max_ndim,
npy_intp out_shape[], int new_ndim,
const npy_intp new_shape[], npy_bool sequence,
enum _dtype_discovery_flags *flags)
{
int success = 0; /* 如果数组不规则,则操作不成功 */
const npy_bool max_dims_reached = *flags & MAX_DIMS_WAS_REACHED;
if (curr_ndim + new_ndim > *max_ndim) {
success = -1;
/* 只更新/检查尽可能多的维度,max_ndim 不改变 */
new_ndim = *max_ndim - curr_ndim;
}
else if (!sequence && (*max_ndim != curr_ndim + new_ndim)) {
/*
* 对于序列,不更新 max_ndim,否则缩减并检查。
* 这是深度优先,因此如果已经设置,out_shape 将填充。
*/
*max_ndim = curr_ndim + new_ndim;
/* 如果已经设置了形状,则数组也不规则 */
if (max_dims_reached) {
success = -1;
}
}
for (int i = 0; i < new_ndim; i++) {
npy_intp curr_dim = out_shape[curr_ndim + i];
npy_intp new_dim = new_shape[i];
if (!max_dims_reached) {
out_shape[curr_ndim + i] = new_dim;
}
else if (new_dim != curr_dim) {
/* 数组不规则,并且此维度已经不可用 */
success = -1;
if (!sequence) {
/* 移除我们不能使用的维度: */
*max_ndim -= new_ndim - i;
}
else {
assert(i == 0);
/* 对于序列,max_ndim 通常不会更新,因此现在设置为: */
*max_ndim = curr_ndim;
}
break;
}
}
if (!sequence) {
*flags |= MAX_DIMS_WAS_REACHED;
}
return success;
}
#ifndef Py_GIL_DISABLED
#define COERCION_CACHE_CACHE_SIZE 5
static int _coercion_cache_num = 0;
static coercion_cache_obj *_coercion_cache_cache[COERCION_CACHE_CACHE_SIZE];
#else
#define COERCION_CACHE_CACHE_SIZE 0
#endif
/*
* 偷取对对象的引用。
*/
static inline int
npy_new_coercion_cache(
PyObject *converted_obj, PyObject *arr_or_sequence, npy_bool sequence,
coercion_cache_obj ***next_ptr, int ndim)
{
coercion_cache_obj *cache;
#if COERCION_CACHE_CACHE_SIZE > 0
if (_coercion_cache_num > 0) {
_coercion_cache_num--;
cache = _coercion_cache_cache[_coercion_cache_num];
}
else
#endif
{
cache = PyMem_Malloc(sizeof(coercion_cache_obj));
}
if (cache == NULL) {
Py_DECREF(arr_or_sequence);
PyErr_NoMemory();
return -1;
}
cache->converted_obj = converted_obj;
cache->arr_or_sequence = arr_or_sequence;
cache->sequence = sequence;
cache->depth = ndim;
cache->next = NULL;
**next_ptr = cache;
*next_ptr = &(cache->next);
return 0;
}
/**
* 取消链接强制转换缓存项。
*
* @param current 当前的强制转换缓存项
* @return 下一个强制转换缓存对象(或 NULL)
*/
# 定义一个名为 npy_unlink_coercion_cache 的函数,用于从缓存中移除一个协同缓存对象,并返回下一个对象。
NPY_NO_EXPORT coercion_cache_obj *
npy_unlink_coercion_cache(coercion_cache_obj *current)
{
// 获取当前对象的下一个对象引用
coercion_cache_obj *next = current->next;
// 减少当前对象关联的数组或序列对象的引用计数
Py_DECREF(current->arr_or_sequence);
#if COERCION_CACHE_CACHE_SIZE > 0
// 如果缓存大小限制大于零,则执行以下操作
if (_coercion_cache_num < COERCION_CACHE_CACHE_SIZE) {
// 将当前对象存储在缓存数组中,并增加缓存计数
_coercion_cache_cache[_coercion_cache_num] = current;
_coercion_cache_num++;
}
else
#endif
{
// 如果缓存已满,则释放当前对象的内存
PyMem_Free(current);
}
// 返回下一个缓存对象
return next;
}
# 定义一个名为 npy_free_coercion_cache 的函数,用于释放从指定对象开始的所有协同缓存对象。
NPY_NO_EXPORT void
npy_free_coercion_cache(coercion_cache_obj *next) {
/* We only need to check from the last used cache pos */
// 只需检查从最后一个使用的缓存位置开始的所有对象
while (next != NULL) {
// 调用 npy_unlink_coercion_cache 函数移除当前对象,并更新下一个对象的引用
next = npy_unlink_coercion_cache(next);
}
}
# 取消定义 COERCION_CACHE_CACHE_SIZE 符号
#undef COERCION_CACHE_CACHE_SIZE
/**
* 处理升级和可能的类型转换步骤。此函数不应在需要描述符的情况下调用。在这种情况下,
* 输出的数据类型不重要,因此我们不能冒险出现升级错误。
*
* @param out_descr 当前的描述符。
* @param descr 要升级的新发现的描述符
* @param fixed_DType 用户提供的(固定的)DType,如果为 NULL 则不提供
* @param flags dtype 发现标志,用于信号升级失败。
* @return -1 表示错误,0 表示成功。
*/
static inline int
handle_promotion(PyArray_Descr **out_descr, PyArray_Descr *descr,
PyArray_DTypeMeta *fixed_DType, enum _dtype_discovery_flags *flags)
{
// 断言描述符未设置标志
assert(!(*flags & DESCRIPTOR_WAS_SET));
// 如果当前描述符为空,则增加新描述符的引用计数,并将其赋值给当前描述符
if (*out_descr == NULL) {
Py_INCREF(descr);
*out_descr = descr;
return 0;
}
// 否则,尝试将新描述符升级为当前描述符的类型
PyArray_Descr *new_descr = PyArray_PromoteTypes(descr, *out_descr);
// 如果升级失败
if (NPY_UNLIKELY(new_descr == NULL)) {
// 如果有固定的 DType 或者捕获到 FutureWarning 异常
if (fixed_DType != NULL || PyErr_ExceptionMatches(PyExc_FutureWarning)) {
/*
* 如果 DType 是固定的,升级不应该失败。不要捕获 FutureWarning
* (用于字符串+数值升级)。这里只能捕获 TypeError 或者总是引发错误。
*/
return -1;
}
PyErr_Clear();
// 设置升级失败的标志,并继续使用对象类型,因为可能需要维度信息
*flags |= PROMOTION_FAILED;
new_descr = PyArray_DescrFromType(NPY_OBJECT);
}
// 设置输出描述符为新描述符
Py_SETREF(*out_descr, new_descr);
return 0;
}
/**
* 处理标量对象,确定数组描述符并处理类型提升。
*
* @param obj 要处理的 Python 对象。
* @param curr_dims 当前维度。
* @param max_dims 最大维度。
* @param out_descr 输出的数组描述符。
* @param out_shape 输出的数组形状。
* @param fixed_DType 固定的数据类型。
* @param flags 类型发现标志。
* @param DType 数据类型元信息。
* @return 处理完成后的最大维度。
*/
handle_scalar(
PyObject *obj, int curr_dims, int *max_dims,
PyArray_Descr **out_descr, npy_intp *out_shape,
PyArray_DTypeMeta *fixed_DType,
enum _dtype_discovery_flags *flags, PyArray_DTypeMeta *DType)
{
PyArray_Descr *descr;
if (update_shape(curr_dims, max_dims, out_shape,
0, NULL, NPY_FALSE, flags) < 0) {
*flags |= FOUND_RAGGED_ARRAY;
return *max_dims;
}
if (*flags & DESCRIPTOR_WAS_SET) {
/* 不需要进行任何类型提升 */
return *max_dims;
}
/* 这是一个标量,因此找到其描述符 */
descr = find_scalar_descriptor(fixed_DType, DType, obj);
if (descr == NULL) {
return -1;
}
if (handle_promotion(out_descr, descr, fixed_DType, flags) < 0) {
Py_DECREF(descr);
return -1;
}
Py_DECREF(descr);
return *max_dims;
}
/**
* 根据数组对象和数据类型类找到正确的描述符。
*
* 这相当于将数组的描述符/数据类型转换为新的数据类型类。
*
* @param arr 数组对象。
* @param DType 要转换到的数据类型类(为 NULL 表示方便起见)。
* @param out_descr 将设置的输出描述符。如果数组是对象类型且没有元素,则结果可以为 NULL。
* @return 失败返回 -1,成功返回 0。
*/
static int
find_descriptor_from_array(
PyArrayObject *arr, PyArray_DTypeMeta *DType, PyArray_Descr **out_descr)
{
enum _dtype_discovery_flags flags = 0;
*out_descr = NULL;
if (DType == NULL) {
*out_descr = PyArray_DESCR(arr);
Py_INCREF(*out_descr);
return 0;
}
/* 其他情况的处理逻辑在这里 */
}
if (NPY_UNLIKELY(NPY_DT_is_parametric(DType) && PyArray_ISOBJECT(arr))) {
/*
* We have one special case, if (and only if) the input array is of
* object DType and the dtype is not fixed already but parametric.
* Then, we allow inspection of all elements, treating them as
* elements. We do this recursively, so nested 0-D arrays can work,
* but nested higher dimensional arrays will lead to an error.
*/
assert(DType->type_num != NPY_OBJECT); /* not parametric */
PyArrayIterObject *iter;
// 创建一个新的迭代器对象,用于遍历数组元素
iter = (PyArrayIterObject *)PyArray_IterNew((PyObject *)arr);
if (iter == NULL) {
return -1;
}
while (iter->index < iter->size) {
PyArray_DTypeMeta *item_DType;
/*
* Note: If the array contains typed objects we may need to use
* the dtype to use casting for finding the correct instance.
*/
// 获取当前迭代指针位置的元素对象
PyObject *elem = PyArray_GETITEM(arr, iter->dataptr);
if (elem == NULL) {
Py_DECREF(iter);
return -1;
}
// 探索元素对象的数据类型
item_DType = discover_dtype_from_pyobject(elem, &flags, DType);
if (item_DType == NULL) {
Py_DECREF(iter);
Py_DECREF(elem);
return -1;
}
if (item_DType == (PyArray_DTypeMeta *)Py_None) {
Py_SETREF(item_DType, NULL);
}
int flat_max_dims = 0;
// 处理标量元素,进行数据类型转换和标量处理
if (handle_scalar(elem, 0, &flat_max_dims, out_descr,
NULL, DType, &flags, item_DType) < 0) {
Py_DECREF(iter);
Py_DECREF(elem);
Py_XDECREF(*out_descr);
Py_XDECREF(item_DType);
return -1;
}
Py_XDECREF(item_DType);
Py_DECREF(elem);
PyArray_ITER_NEXT(iter); // 移动迭代器到下一个元素位置
}
Py_DECREF(iter); // 释放迭代器对象
}
else if (NPY_UNLIKELY(DType->type_num == NPY_DATETIME) &&
PyArray_ISSTRING(arr)) {
/*
* TODO: This branch should be deprecated IMO, the workaround is
* to cast to the object to a string array. Although a specific
* function (if there is even any need) would be better.
* This is value based casting!
* Unless of course we actually want to support this kind of thing
* in general (not just for object dtype)...
*/
// 处理特殊情况:如果数组的数据类型为日期时间类型,而数组本身为字符串数组
PyArray_DatetimeMetaData meta;
meta.base = NPY_FR_GENERIC;
meta.num = 1;
// 尝试找到字符串数组的 datetime64 类型
if (find_string_array_datetime64_type(arr, &meta) < 0) {
return -1;
}
else {
// 根据找到的 datetime64 类型创建日期时间数据类型描述符
*out_descr = create_datetime_dtype(NPY_DATETIME, &meta);
if (*out_descr == NULL) {
return -1;
}
}
}
else {
/*
* 如果不是对象数组,则确定数据类型的转换,或者直接使用返回的 DType。
*/
// 将数组的描述符转换为指定的 DType 类型
*out_descr = PyArray_CastDescrToDType(PyArray_DESCR(arr), DType);
// 如果转换失败,则返回错误码 -1
if (*out_descr == NULL) {
return -1;
}
}
// 返回成功码 0
return 0;
/**
* Given a dtype or DType object, find the correct descriptor to cast the
* array to. In some places, this function is used with dtype=NULL which
* means that legacy behavior is used: The dtype instances "S0", "U0", and
* "V0" are converted to mean the DType classes instead.
* When dtype != NULL, this path is ignored, and the function does nothing
* unless descr == NULL. If both descr and dtype are null, it returns the
* descriptor for the array.
*
* This function is identical to normal casting using only the dtype, however,
* it supports inspecting the elements when the array has object dtype
* (and the given datatype describes a parametric DType class).
*
* @param arr PyArrayObject* representing the input array
* @param dtype NULL or a dtype class
* @param descr A dtype instance, if the dtype is NULL the dtype class is
* found and e.g. "S0" is converted to denote only String.
* @return A concrete dtype instance or NULL
*/
NPY_NO_EXPORT PyArray_Descr *
PyArray_AdaptDescriptorToArray(
PyArrayObject *arr, PyArray_DTypeMeta *dtype, PyArray_Descr *descr)
{
/* If the requested dtype is flexible, adapt it */
PyArray_Descr *new_descr;
int res;
// If both dtype and descr are provided, return descr directly
if (dtype != NULL && descr != NULL) {
/* descr was given and no special logic, return (call not necessary) */
Py_INCREF(descr);
return descr;
}
// If dtype is NULL, extract dtype and descriptor
if (dtype == NULL) {
// Extract dtype and descriptor
res = PyArray_ExtractDTypeAndDescriptor(descr, &new_descr, &dtype);
if (res < 0) {
return NULL;
}
// If new_descr is found, return it and release dtype
if (new_descr != NULL) {
Py_DECREF(dtype);
return new_descr;
}
}
else {
// If dtype is not NULL and descr is NULL, just return dtype
assert(descr == NULL); /* gueranteed above */
Py_INCREF(dtype);
}
// Find descriptor from array and update new_descr
res = find_descriptor_from_array(arr, dtype, &new_descr);
if (res < 0) {
Py_DECREF(dtype);
return NULL;
}
// If new_descr is still NULL, handle object array case
if (new_descr == NULL) {
/* This is an object array but contained no elements, use default */
new_descr = NPY_DT_CALL_default_descr(dtype);
}
// Release dtype and return new_descr
Py_XDECREF(dtype);
return new_descr;
}
/**
* Recursion helper for `PyArray_DiscoverDTypeAndShape`. See its
* documentation for additional details.
*
* @param obj The current (possibly nested) object
* 当前(可能是嵌套的)对象
* @param curr_dims The current depth, i.e. initially 0 and increasing.
* 当前深度,初始为0,递增。
* @param max_dims Maximum number of dimensions, modified during discovery.
* 最大维度数,在发现过程中被修改。
* @param out_descr dtype instance (or NULL) to promoted and update.
* 用于推广和更新的 dtype 实例(或空)
* @param out_shape The current shape (updated)
* 当前形状(已更新)
* @param coercion_cache_tail_ptr The tail of the linked list of coercion
* cache objects, which hold on to converted sequences and arrays.
* 用于强制转换缓存对象链表的尾部,保存转换的序列和数组。
* @param fixed_DType User provided fixed DType class
* 用户提供的固定 DType 类
* @param flags Discovery flags (reporting and behaviour flags, see def.)
* 发现标志(报告和行为标志,详见定义)
* @param copy Specifies the copy behavior. -1 is corresponds to copy=None,
* 0 to copy=False, and 1 to copy=True in the Python API.
* 指定复制行为。-1 对应于 Python API 中的 copy=None,
* 0 对应于 copy=False,1 对应于 copy=True。
* @return The updated number of maximum dimensions (i.e. scalars will set
* this to the current dimensions).
* 更新后的最大维度数(即标量将其设置为当前维度)。
*/
NPY_NO_EXPORT int
PyArray_DiscoverDTypeAndShape_Recursive(
PyObject *obj, int curr_dims, int max_dims, PyArray_Descr**out_descr,
npy_intp out_shape[NPY_MAXDIMS],
coercion_cache_obj ***coercion_cache_tail_ptr,
PyArray_DTypeMeta *fixed_DType, enum _dtype_discovery_flags *flags,
int copy)
{
PyArrayObject *arr = NULL;
PyObject *seq;
/*
* The first step is to find the DType class if it was not provided,
* alternatively we have to find out that this is not a scalar at all
* (which could fail and lead us to `object` dtype).
*
* 首先是如果没有提供 DType 类,则找到对应的 DType 类,
* 或者我们必须确认这根本不是标量(可能会失败并导致使用 `object` dtype)。
*/
PyArray_DTypeMeta *DType = NULL;
if (NPY_UNLIKELY(*flags & DISCOVER_STRINGS_AS_SEQUENCES)) {
/*
* We currently support that bytes/strings are considered sequences,
* if the dtype is np.dtype('c'), this should be deprecated probably,
* but requires hacks right now.
*
* 我们目前支持将字节/字符串视为序列,
* 如果 dtype 是 np.dtype('c'),这可能应该被弃用,但目前需要一些技巧。
*/
if (PyBytes_Check(obj) && PyBytes_Size(obj) != 1) {
goto force_sequence_due_to_char_dtype;
}
else if (PyUnicode_Check(obj) && PyUnicode_GetLength(obj) != 1) {
goto force_sequence_due_to_char_dtype;
}
}
/* If this is a known scalar, find the corresponding DType class */
// 如果这是已知的标量,则找到对应的 DType 类
DType = discover_dtype_from_pyobject(obj, flags, fixed_DType);
if (DType == NULL) {
return -1;
}
else if (DType == (PyArray_DTypeMeta *)Py_None) {
Py_DECREF(Py_None);
}
else {
max_dims = handle_scalar(
obj, curr_dims, &max_dims, out_descr, out_shape, fixed_DType,
flags, DType);
Py_DECREF(DType);
return max_dims;
}
force_sequence_due_to_char_dtype:
// 强制将字符 dtype 视为序列的情况
// (此处可能包含特定的处理逻辑,但在注释中未提及具体实现细节)
return -1; // 返回错误状态
}
/*
* 此时我们期望找到一个序列或类似数组的对象。
* 尽管仍然可能失败并且需要使用 `object`。
*/
if (PyArray_Check(obj)) {
// 如果 obj 是一个 NumPy 数组,则直接使用它
arr = (PyArrayObject *)obj;
// 增加数组对象的引用计数,确保不会被释放
Py_INCREF(arr);
}
else {
// 如果不是 NumPy 数组,则尝试从类似数组对象创建一个数组对象
PyArray_Descr *requested_descr = NULL;
if (*flags & DESCRIPTOR_WAS_SET) {
/* 如果已经设置了描述符,__array__ 可能会使用请求的描述符 */
requested_descr = *out_descr;
}
int was_copied_by__array__ = 0;
// 通过 _array_from_array_like 函数创建数组对象
arr = (PyArrayObject *)_array_from_array_like(obj,
requested_descr, 0, NULL, copy, &was_copied_by__array__);
if (arr == NULL) {
return -1; // 创建数组对象失败,返回错误
}
else if (arr == (PyArrayObject *)Py_NotImplemented) {
Py_DECREF(arr);
arr = NULL;
}
if (was_copied_by__array__ == 1) {
*flags |= COPY_WAS_CREATED_BY__ARRAY__;
}
}
if (arr != NULL) {
/*
* 这是一个数组对象,将被添加到缓存中,保持数组的引用(拥有所有权)。
*/
// 将数组对象添加到类型强制缓存中
if (npy_new_coercion_cache(obj, (PyObject *)arr,
0, coercion_cache_tail_ptr, curr_dims) < 0) {
return -1; // 添加缓存失败,返回错误
}
if (curr_dims == 0) {
/*
* 对于反向广播的特殊情况,如果只有一个类似数组对象,则忽略 max_dims;
* 这对 PyArray_CopyObject 是必需的。
*/
// 复制数组对象的形状到 out_shape,设置 max_dims
memcpy(out_shape, PyArray_SHAPE(arr),
PyArray_NDIM(arr) * sizeof(npy_intp));
max_dims = PyArray_NDIM(arr);
}
else if (update_shape(curr_dims, &max_dims, out_shape,
PyArray_NDIM(arr), PyArray_SHAPE(arr), NPY_FALSE, flags) < 0) {
*flags |= FOUND_RAGGED_ARRAY;
return max_dims; // 更新形状失败,返回 max_dims
}
if (*flags & DESCRIPTOR_WAS_SET) {
return max_dims; // 如果已设置描述符,则返回 max_dims
}
/*
* 对于数组,我们可能不仅需要将 dtype 转换为用户提供的 fixed_DType;
* 如果这是一个对象数组,则可能需要逐个检查元素。
* 注意,首先找到数组的描述符,然后再提升(不同的结合性)。
*/
// 查找数组的描述符,并根据需要进行提升
PyArray_Descr *cast_descr;
if (find_descriptor_from_array(arr, fixed_DType, &cast_descr) < 0) {
return -1; // 查找描述符失败,返回错误
}
if (cast_descr == NULL) {
/* 对象数组没有元素,无需提升/调整。 */
return max_dims; // 如果 cast_descr 为 NULL,返回 max_dims
}
if (handle_promotion(out_descr, cast_descr, fixed_DType, flags) < 0) {
Py_DECREF(cast_descr);
return -1; // 处理提升失败,返回错误
}
Py_DECREF(cast_descr);
return max_dims; // 成功处理提升,返回 max_dims
}
/*
* 首先检查对象是否被视为序列,并递归处理。除非达到了维度限制。
*/
npy_bool is_sequence = PySequence_Check(obj);
// 如果对象是序列
if (is_sequence) {
// 检查序列的大小是否大于等于0
is_sequence = PySequence_Size(obj) >= 0;
// 如果不是序列,处理错误
if (NPY_UNLIKELY(!is_sequence)) {
/* 注意:这里可能只是引发所有的错误 */
// 如果引发了递归错误或内存错误,认为是无法恢复的错误,终止执行
if (PyErr_ExceptionMatches(PyExc_RecursionError) ||
PyErr_ExceptionMatches(PyExc_MemoryError)) {
/*
* 这些是无法恢复的错误,继续执行可能导致解释器崩溃。
*/
return -1;
}
// 清除异常状态
PyErr_Clear();
}
}
// 如果禁用了元组作为元素的发现,并且对象是元组,则不视为序列
if (NPY_UNLIKELY(*flags & DISCOVER_TUPLES_AS_ELEMENTS) &&
PyTuple_Check(obj)) {
is_sequence = NPY_FALSE;
}
// 如果当前维度已达到最大维度或对象不是序列,则处理为标量
if (curr_dims == max_dims || !is_sequence) {
/* 清除任何可能导致后续调用出错的 PySequence_Size 错误 */
// 处理对象为标量,更新相关参数
max_dims = handle_scalar(
obj, curr_dims, &max_dims, out_descr, out_shape, fixed_DType,
flags, NULL);
// 如果对象是序列,则标记为不规则数组或过深的数组
if (is_sequence) {
*flags |= FOUND_RAGGED_ARRAY;
}
return max_dims;
}
/* 如果我们停止支持 bytes/str 的子类,这里可能需要更多处理: */
// 确保对象不是 bytes 或 str 的子类
assert(!PyBytes_Check(obj) && !PyUnicode_Check(obj));
force_sequence_due_to_char_dtype:
/*
* 确保我们有一个序列(对于 PyPy 是必需的)
*/
// 尝试将对象转换为快速序列,如果失败,则处理特定的错误
seq = PySequence_Fast(obj, "Could not convert object to sequence");
if (seq == NULL) {
/*
* 特别处理类似于字典的对象,视为标量处理
*/
// 如果是 KeyError 不报错,而是视为标量处理
if (PyErr_ExceptionMatches(PyExc_KeyError)) {
PyErr_Clear();
// 处理对象为标量,更新相关参数
max_dims = handle_scalar(
obj, curr_dims, &max_dims, out_descr, out_shape, fixed_DType,
flags, NULL);
return max_dims;
}
// 其他异常情况,返回错误
return -1;
}
/* 缓存在这里获取序列的所有权 */
// 将序列对象和原始对象加入类型转换缓存
if (npy_new_coercion_cache(obj, seq, 1, coercion_cache_tail_ptr, curr_dims) < 0) {
return -1;
}
// 获取序列的大小和对象数组
npy_intp size = PySequence_Fast_GET_SIZE(seq);
PyObject **objects = PySequence_Fast_ITEMS(seq);
// 更新形状,如果是不规则的情况,则进行更新
if (update_shape(curr_dims, &max_dims,
out_shape, 1, &size, NPY_TRUE, flags) < 0) {
// 如果是不规则的情况,则标记
*flags |= FOUND_RAGGED_ARRAY;
return max_dims;
}
// 如果序列为空,则表示这是最后一个维度
if (size == 0) {
// 如果序列为空,标记已达到最大维度
*flags |= MAX_DIMS_WAS_REACHED;
return curr_dims + 1;
}
/* 允许处理键盘中断。参见 gh issue 18117。*/
// 检查是否有键盘中断信号
if (PyErr_CheckSignals() < 0) {
return -1;
}
/*
* 对于一个序列,我们无论如何都需要复制最终的聚合结果。
* 因此不需要显式传递 `copy=True`,所以我们将 `copy=None`(如果需要则复制)。
*/
如果 (copy == 1) {
copy = -1; // 将 copy 设置为 -1,表示需要复制
}
/* 递归调用每个序列项 */
for (Py_ssize_t i = 0; i < size; i++) {
max_dims = PyArray_DiscoverDTypeAndShape_Recursive(
objects[i], curr_dims + 1, max_dims,
out_descr, out_shape, coercion_cache_tail_ptr, fixed_DType,
flags, copy);
if (max_dims < 0) {
return -1; // 如果递归调用返回负值,直接返回 -1
}
}
return max_dims; // 返回计算出的最大维度
/**
* Finds the DType and shape of an arbitrary nested sequence. This is the
* general purpose function to find the parameters of the array (but not
* the array itself) as returned by `np.array()`
*
* Note: Before considering to make part of this public, we should consider
* whether things such as `out_descr != NULL` should be supported in
* a public API.
*
* @param obj Scalar or nested sequences.
* 输入参数:标量或嵌套序列。
* @param max_dims Maximum number of dimensions (after this scalars are forced)
* 最大维度数(超过这个数后标量被强制处理)。
* @param out_shape Will be filled with the output shape (more than the actual
* shape may be written).
* 输出参数:将被填充为输出形状(可能写入比实际形状更多的内容)。
* @param coercion_cache NULL initialized reference to a cache pointer.
* May be set to the first coercion_cache, and has to be freed using
* npy_free_coercion_cache.
* This should be stored in a thread-safe manner (i.e. function static)
* and is designed to be consumed by `PyArray_AssignFromCache`.
* If not consumed, must be freed using `npy_free_coercion_cache`.
* 用于缓存指针的空初始化引用。
* 可能被设置为第一个 coercion_cache,并且必须使用 npy_free_coercion_cache 进行释放。
* 应以线程安全的方式存储(即函数静态),并设计用于 `PyArray_AssignFromCache` 的消耗。
* 如果未被消耗,必须使用 `npy_free_coercion_cache` 进行释放。
* @param fixed_DType A user provided fixed DType class.
* 用户提供的固定 DType 类。
* @param requested_descr A user provided fixed descriptor. This is always
* returned as the discovered descriptor, but currently only used
* for the ``__array__`` protocol.
* 用户提供的固定描述符。这总是作为发现的描述符返回,但目前仅用于 ``__array__`` 协议。
* @param out_descr Set to the discovered output descriptor. This may be
* non NULL but only when fixed_DType/requested_descr are not given.
* If non NULL, it is the first dtype being promoted and used if there
* are no elements.
* The result may be unchanged (remain NULL) when converting a
* sequence with no elements. In this case it is callers responsibility
* to choose a default.
* 设置为发现的输出描述符。这可能是非 NULL,但只有在没有给定 fixed_DType/requested_descr 时才是如此。
* 如果非 NULL,则它是被提升并用于没有元素时的第一个 dtype。
* 当转换一个没有元素的序列时,结果可能不变(保持 NULL)。在这种情况下,由调用者负责选择默认值。
* @param copy Specifies the copy behavior. -1 is corresponds to copy=None,
* 0 to copy=False, and 1 to copy=True in the Python API.
* 指定复制行为。-1 对应于 Python API 中的 copy=None,0 对应于 copy=False,1 对应于 copy=True。
* @param was_copied_by__array__ Set to 1 if it can be assumed that a copy was
* made by implementor.
* 如果可以假定实现者已经进行了复制,则设置为 1。
* @return dimensions of the discovered object or -1 on error.
* WARNING: If (and only if) the output is a single array, the ndim
* returned _can_ exceed the maximum allowed number of dimensions.
* It might be nice to deprecate this? But it allows things such as
* `arr1d[...] = np.array([[1,2,3,4]])`
* 发现对象的维度或错误时返回 -1。
* 警告:如果输出是单个数组,则返回的 ndim 可以超过允许的最大维数。
* 这可能会被弃用?但它允许诸如 `arr1d[...] = np.array([[1,2,3,4]])` 的操作。
*/
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__)
{
coercion_cache_obj **coercion_cache_head = coercion_cache;
*coercion_cache = NULL;
enum _dtype_discovery_flags flags = 0;
/*
* Support a passed in descriptor (but only if nothing was specified).
*/
assert(*out_descr == NULL || fixed_DType == NULL);
/* Validate input of requested descriptor and DType */
}
# 如果 fixed_DType 不为空指针,则进行断言检查其类型是否为 PyArrayDTypeMeta_Type
if (fixed_DType != NULL) {
assert(PyObject_TypeCheck(
(PyObject *)fixed_DType, (PyTypeObject *)&PyArrayDTypeMeta_Type));
}
# 如果 requested_descr 不为空指针,则执行以下操作
if (requested_descr != NULL) {
# 如果 fixed_DType 不为空指针,则断言 fixed_DType 是否等于 requested_descr 的 NPY_DTYPE
if (fixed_DType != NULL) {
assert(fixed_DType == NPY_DTYPE(requested_descr));
}
# 将 requested_descr 作为输出描述符,并增加其引用计数
Py_INCREF(requested_descr);
*out_descr = requested_descr;
# 设置标志位,表示描述符已经设置
flags |= DESCRIPTOR_WAS_SET;
}
/*
* 调用递归函数,可能需要扩展设置以更好地处理缓存。
*/
/* 遗留发现标志 */
if (requested_descr != NULL) {
# 如果 requested_descr 的类型编号为 NPY_STRING,且其类型为 'c'
if (requested_descr->type_num == NPY_STRING &&
requested_descr->type == 'c') {
/* 字符类型的字符串变体(应该已被弃用...) */
flags |= DISCOVER_STRINGS_AS_SEQUENCES;
}
# 如果 requested_descr 的类型编号为 NPY_VOID,并且具有命名字段或子数组
else if (requested_descr->type_num == NPY_VOID &&
(((_PyArray_LegacyDescr *)requested_descr)->names
|| ((_PyArray_LegacyDescr *)requested_descr)->subarray)) {
/* Void 类型是一个嵌合体,可能是结构化的也可能不是... */
flags |= DISCOVER_TUPLES_AS_ELEMENTS;
}
}
# 调用 PyArray_DiscoverDTypeAndShape_Recursive 函数来发现数据类型和形状
int ndim = PyArray_DiscoverDTypeAndShape_Recursive(
obj, 0, max_dims, out_descr, out_shape, &coercion_cache,
fixed_DType, &flags, copy);
# 如果返回值小于 0,则跳转到失败处理标签
if (ndim < 0) {
goto fail;
}
# 如果 was_copied_by__array__ 不为空且标志位中包含 COPY_WAS_CREATED_BY__ARRAY__
if (was_copied_by__array__ != NULL && flags & COPY_WAS_CREATED_BY__ARRAY__) {
# 设置 was_copied_by__array__ 为 1,表示由 __array__ 创建了拷贝
*was_copied_by__array__ = 1;
}
if (NPY_UNLIKELY(flags & FOUND_RAGGED_ARRAY)) {
/*
* 如果标志中包含 FOUND_RAGGED_ARRAY,说明发现了不规则数组。
* 这可能是由于达到了最大维度并且维度被减少,这种情况被称为不规则数组。
* 否则,我们仅仅是达到了最大维度,这种情况稍微有所不同。
* 例如,对于 `[1, [2, 3]]` 这样的数组,最大维度是 1,但是发现了序列。
*
* 在这种情况下,我们需要通知用户并清除缓存,因为可能太深了。
*/
/* 处理达到最大深度的情况: */
int too_deep = ndim == max_dims;
if (fixed_DType == NULL || fixed_DType->type_num != NPY_OBJECT) {
/* 只有对象类型的数据类型支持不规则数组的情况,统一报错 */
if (!too_deep) {
PyObject *shape = PyArray_IntTupleFromIntp(ndim, out_shape);
PyErr_Format(PyExc_ValueError,
"setting an array element with a sequence. The "
"requested array has an inhomogeneous shape after "
"%d dimensions. The detected shape was "
"%R + inhomogeneous part.",
ndim, shape);
Py_DECREF(shape);
}
else {
PyErr_Format(PyExc_ValueError,
"setting an array element with a sequence. The "
"requested array would exceed the maximum number of "
"dimension of %d.",
max_dims);
}
goto fail;
}
/*
* 如果数组是不规则的,缓存可能太深,因此需要清理它。
* 但是缓存的深度将与数组保持一致。
*/
coercion_cache_obj **next_ptr = coercion_cache_head;
coercion_cache_obj *current = *coercion_cache_head; /* 要检查的项目 */
while (current != NULL) {
if (current->depth > ndim) {
/* 删除 "next" 缓存项并将指针前进(与后续操作不同) */
current = npy_unlink_coercion_cache(current);
continue;
}
/* 将 prev 和 next 都前进,并将 prev->next 设置为新项 */
*next_ptr = current;
next_ptr = &(current->next);
current = current->next;
}
*next_ptr = NULL;
}
/* 这里也可以检查是否达到了最大维度 */
if (requested_descr != NULL) {
/* 如果提供了描述符,则确保我们没有意外更改它 */
assert(*out_descr == requested_descr);
}
else if (NPY_UNLIKELY(*out_descr == NULL)) {
/*
* 如果输出描述符指针指向 NULL,则执行以下代码块:
* 当对象不包含任何元素(长度为零的序列)时,可能找不到描述符。
* 如果请求了固定的数据类型(DType),则使用它来定义输出的数据类型。
* 否则,out_descr 将保持为 NULL,调用者需要设置正确的默认值。
*/
if (fixed_DType != NULL) {
// 使用固定的数据类型(DType)调用函数获取默认描述符
*out_descr = NPY_DT_CALL_default_descr(fixed_DType);
// 检查描述符是否成功获取,如果为 NULL,则跳转到失败处理标签
if (*out_descr == NULL) {
goto fail;
}
}
}
// 返回数组的维度
return ndim;
fail:
// 释放强制转换缓存
npy_free_coercion_cache(*coercion_cache_head);
// 将强制转换缓存头指针设置为 NULL
*coercion_cache_head = NULL;
// 将输出描述符设置为 NULL
Py_XSETREF(*out_descr, NULL);
// 返回错误码 -1
return -1;
/*
* Python API function to expose the dtype+shape discovery functionality
* directly.
*/
NPY_NO_EXPORT PyObject *
_discover_array_parameters(PyObject *NPY_UNUSED(self),
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
PyObject *obj; // 声明一个PyObject类型的变量obj,用于存储传入的Python对象
npy_dtype_info dt_info = {NULL, NULL}; // 声明并初始化一个npy_dtype_info结构体变量dt_info,用于存储dtype信息
npy_intp shape[NPY_MAXDIMS]; // 声明一个长度为NPY_MAXDIMS的npy_intp类型数组shape,用于存储数组的形状
NPY_PREPARE_ARGPARSER; // 准备解析参数的宏定义,这里将在后续代码中使用
// 解析传入的参数
if (npy_parse_arguments(
"_discover_array_parameters", args, len_args, kwnames,
"", NULL, &obj,
"|dtype", &PyArray_DTypeOrDescrConverterOptional, &dt_info,
NULL, NULL, NULL) < 0) {
/* fixed is last to parse, so never necessary to clean up */
return NULL; // 解析参数失败时返回NULL
}
coercion_cache_obj *coercion_cache = NULL; // 声明一个coercion_cache_obj类型指针变量coercion_cache,并初始化为NULL
PyObject *out_dtype = NULL; // 声明一个PyObject类型指针变量out_dtype,并初始化为NULL
int ndim = PyArray_DiscoverDTypeAndShape(
obj, NPY_MAXDIMS, shape,
&coercion_cache,
dt_info.dtype, dt_info.descr, (PyArray_Descr **)&out_dtype, 0, NULL); // 调用PyArray_DiscoverDTypeAndShape函数获取数组的dtype和形状信息
Py_XDECREF(dt_info.dtype); // 安全释放dt_info中的dtype成员
Py_XDECREF(dt_info.descr); // 安全释放dt_info中的descr成员
if (ndim < 0) {
return NULL; // 获取dtype和形状信息失败时返回NULL
}
npy_free_coercion_cache(coercion_cache); // 释放类型转换缓存对象的内存
if (out_dtype == NULL) {
/* Empty sequence, report this as None. */
out_dtype = Py_None; // 如果dtype为空,将out_dtype设置为Py_None
Py_INCREF(Py_None); // 增加Py_None的引用计数
}
PyObject *shape_tuple = PyArray_IntTupleFromIntp(ndim, shape); // 将ndim和shape数组转换为Python元组对象
if (shape_tuple == NULL) {
return NULL; // 转换为元组失败时返回NULL
}
PyObject *res = PyTuple_Pack(2, (PyObject *)out_dtype, shape_tuple); // 将out_dtype和shape_tuple打包成一个元组对象res
Py_DECREF(out_dtype); // 减少out_dtype的引用计数
Py_DECREF(shape_tuple); // 减少shape_tuple的引用计数
return res; // 返回包含dtype和形状信息的元组对象
}