NumPy 源码解析(七十二)
.\numpy\numpy\_core\src\multiarray\stringdtype\dtype.h
extern "C" {
// static string library中未公开的内容,因此需要在这里定义
// 这样可以在描述符上定义 `elsize` 和 `alignment`
//
// 如果 `npy_packed_static_string` 的布局在将来发生变化,可能需要更新此处内容。
// 返回一个新的字符串类型实例
NPY_NO_EXPORT PyObject *
new_stringdtype_instance(PyObject *na_object, int coerce);
// 初始化字符串类型的相关内容
NPY_NO_EXPORT int
init_string_dtype(void);
// 假设调用者已经获取了两个描述符的分配器锁
NPY_NO_EXPORT int
_compare(void *a, void *b, PyArray_StringDTypeObject *descr_a,
PyArray_StringDTypeObject *descr_b);
// 初始化字符串类型的 NA 对象
NPY_NO_EXPORT int
init_string_na_object(PyObject *mod);
// 设置字符串类型的项目
NPY_NO_EXPORT int
stringdtype_setitem(PyArray_StringDTypeObject *descr, PyObject *obj, char **dataptr);
// 在调用此函数之前,必须先获取两个分配器的锁
NPY_NO_EXPORT int
free_and_copy(npy_string_allocator *in_allocator,
npy_string_allocator *out_allocator,
const npy_packed_static_string *in,
npy_packed_static_string *out, const char *location);
// 加载新的字符串
NPY_NO_EXPORT int
load_new_string(npy_packed_static_string *out, npy_static_string *out_ss,
size_t num_bytes, npy_string_allocator *allocator,
const char *err_context);
// 最终化字符串类型的描述符
NPY_NO_EXPORT PyArray_Descr *
stringdtype_finalize_descr(PyArray_Descr *dtype);
// 执行等于比较
NPY_NO_EXPORT int
_eq_comparison(int scoerce, int ocoerce, PyObject *sna, PyObject *ona);
// 检查两个 NA 对象是否兼容
NPY_NO_EXPORT int
stringdtype_compatible_na(PyObject *na1, PyObject *na2, PyObject **out_na);
}
.\numpy\numpy\_core\src\multiarray\stringdtype\static_string.c
/*
* Static string API
*
* Strings can be stored in multiple ways. Initialization leaves them as
* either initialized or missing, with initialization being inside an arena
* except for short strings that fit inside the static string struct stored in
* the array buffer (<=15 or 7 bytes, depending on architecture).
*
* If a string is replaced, it will be allocated on the heap if it cannot fit
* inside the original short string or arena allocation. If a string is set
* to missing, the information about the previous allocation is kept, so
* replacement with a new string can use the possible previous arena
* allocation. Note that after replacement with a short string, any arena
* information is lost, so a later replacement with a longer one will always
* be on the heap.
*/
// work around Python 3.10 and earlier issue, see
// the commit message of 82fd2b8 for more details
// also needed for the allocator mutex
// the last and hence highest byte in vstring.size is reserved for flags
//
// SSSS SSSF
typedef struct _npy_static_vstring_t {
size_t offset;
size_t size_and_flags;
} _npy_static_vstring_t;
typedef struct _short_string_buffer {
char buf[sizeof(_npy_static_vstring_t) - 1];
unsigned char size_and_flags;
} _short_string_buffer;
// the first and hence highest byte in vstring.size is reserved for flags
//
// FSSS SSSS
typedef struct _npy_static_vstring_t {
size_t size_and_flags;
size_t offset;
} _npy_static_vstring_t;
typedef struct _short_string_buffer {
unsigned char size_and_flags;
char buf[sizeof(_npy_static_vstring_t) - 1];
} _short_string_buffer;
typedef union _npy_static_string_u {
_npy_static_vstring_t vstring;
_short_string_buffer direct_buffer;
} _npy_static_string_u;
// Flags defining whether a string exists and how it is stored.
// Inside the arena, long means not medium (i.e., >255 bytes), while
// outside, it means not short (i.e., >15 or 7, depending on arch).
// set for null strings representing missing data
// set after an array entry is initialized for the first time
// The string data is managed by malloc/free on the heap
// Only set for strings that have been mutated to be longer
// than the original entry
// A string that lives in the arena with a size longer
// than 255 bytes, so the size in the arena is stored in a size_t
// 定义一个掩码,用于提取字符串标志字节中未使用的最后四位
// 定义一个掩码,用于提取短字符串的大小,适合于4位整数
// NPY_SHORT_STRING_MAX_SIZE根据系统架构确定,表示短字符串的最大长度
(sizeof(npy_static_string) - 1) // 15 or 7 depending on arch
// NPY_MEDIUM_STRING_MAX_SIZE定义中等字符串的最大长度
// 定义宏,用于从字符串结构中获取其标志位
(((_npy_static_string_u *)string)->direct_buffer.size_and_flags & NPY_STRING_FLAG_MASK)
// 定义宏,用于从字符串结构中获取短字符串的大小
(string->direct_buffer.size_and_flags & NPY_SHORT_STRING_SIZE_MASK)
// 定义掩码,用于提取高位字节
// 定义宏,用于获取可变长度字符串的大小
// 空字符串结构的静态实例,用于表示未初始化的、中等大小的字符串
static const _npy_static_string_u empty_string_u = {
.direct_buffer = {.size_and_flags = 0, .buf = {0}}};
// 函数:判断给定的静态字符串是否为短字符串
static int
is_short_string(const npy_packed_static_string *s)
{
// 检查字符串的标志是否表明它是一个初始化并且在区域外的短字符串
return STRING_FLAGS(s) == (NPY_STRING_INITIALIZED | NPY_STRING_OUTSIDE_ARENA);
}
// 结构体:定义字符串内存分配器的状态
typedef struct npy_string_arena {
size_t cursor; // 当前位置指针
size_t size; // 缓冲区大小
char *buffer; // 缓冲区指针
} npy_string_arena;
// 结构体:定义字符串分配器的具体实现
struct npy_string_allocator {
npy_string_malloc_func malloc; // 内存分配函数指针
npy_string_free_func free; // 内存释放函数指针
npy_string_realloc_func realloc; // 内存重新分配函数指针
npy_string_arena arena; // 字符串区域
PyThread_type_lock *allocator_lock; // 分配器锁指针
};
// 函数:设置可变长度字符串的大小
static void
set_vstring_size(_npy_static_string_u *str, size_t size)
{
unsigned char current_flags = str->direct_buffer.size_and_flags;
str->vstring.size_and_flags = size; // 设置可变长度字符串的大小
str->direct_buffer.size_and_flags = current_flags; // 保持原有标志位
}
// 函数:获取可变长度字符串的缓冲区指针
static char *
vstring_buffer(npy_string_arena *arena, _npy_static_string_u *string)
{
// 如果字符串标志表明其在区域外,则直接返回偏移量作为缓冲区指针
if (STRING_FLAGS(string) & NPY_STRING_OUTSIDE_ARENA) {
return (char *)string->vstring.offset;
}
// 如果字符串在区域内但是缓冲区为空,则返回空指针
if (arena->buffer == NULL) {
return NULL;
}
// 否则,返回缓冲区起始地址加上偏移量作为缓冲区指针
return (char *)((size_t)arena->buffer + string->vstring.offset);
}
// 宏:定义缓冲区扩展因子为1.25倍
// 函数:在字符串区域中进行内存分配
static char *
arena_malloc(npy_string_arena *arena, npy_string_realloc_func r, size_t size)
{
// 计算实际存储空间大小,需要额外一个size_t来存储分配的大小信息
size_t string_storage_size;
if (size <= NPY_MEDIUM_STRING_MAX_SIZE) {
string_storage_size = size + sizeof(unsigned char);
}
else {
string_storage_size = size + sizeof(size_t);
}
// 检查剩余的空间是否足够存储新数据,若不足则进行重新分配
if ((arena->size - arena->cursor) <= string_storage_size) {
// 重新分配缓冲区,确保有足够的空间
// 初始猜测是将缓冲区的大小加倍
size_t newsize;
if (arena->size == 0) {
newsize = string_storage_size;
}
else if (((ARENA_EXPAND_FACTOR * arena->size) - arena->cursor) >
string_storage_size) {
newsize = ARENA_EXPAND_FACTOR * arena->size;
}
else {
newsize = arena->size + string_storage_size;
}
if ((arena->cursor + size) >= newsize) {
// 如果需要比扩展因子更多的额外空间,留出一些填充
newsize = ARENA_EXPAND_FACTOR * (arena->cursor + size);
}
// 调用 realloc 函数重新分配内存,若传入的旧缓冲区为 NULL,则行为类似 malloc
char *newbuf = r(arena->buffer, newsize);
if (newbuf == NULL) {
return NULL;
}
// 将新分配的部分清零,以防止出现未初始化的内存
memset(newbuf + arena->cursor, 0, newsize - arena->cursor);
arena->buffer = newbuf;
arena->size = newsize;
}
// 根据字符串大小选择存储方式
char *ret;
if (size <= NPY_MEDIUM_STRING_MAX_SIZE) {
// 对于较小的字符串,将字符串大小存储在缓冲区中,并返回字符串数据的指针
unsigned char *size_loc =
(unsigned char *)&arena->buffer[arena->cursor];
*size_loc = size;
ret = &arena->buffer[arena->cursor + sizeof(char)];
}
else {
// 对于较大的字符串,直接将大小值存储在缓冲区中,并返回字符串数据的指针
char *size_ptr = (char *)&arena->buffer[arena->cursor];
memcpy(size_ptr, &size, sizeof(size_t));
ret = &arena->buffer[arena->cursor + sizeof(size_t)];
}
// 更新游标位置,指向下一个可用空间的起始位置
arena->cursor += string_storage_size;
// 返回指向存储字符串数据的指针
return ret;
static int
arena_free(npy_string_arena *arena, _npy_static_string_u *str)
{
// 计算字符串大小
size_t size = VSTRING_SIZE(str);
// 断言字符串大小应大于 0
assert (size > 0);
// 断言 arena 不为空
assert (!(arena->size == 0 && arena->cursor == 0 && arena->buffer == NULL));
// 获取字符串的指针
char *ptr = vstring_buffer(arena, str);
// 如果指针为空,返回错误
if (ptr == NULL) {
return -1;
}
// 将 arena 的起始地址、ptr 和结束地址进行 uintptr_t 类型转换
uintptr_t buf_start = (uintptr_t)arena->buffer;
uintptr_t ptr_loc = (uintptr_t)ptr;
uintptr_t end_loc = ptr_loc + size;
uintptr_t buf_end = buf_start + arena->size;
// 检查指针和地址范围是否有效
if (ptr_loc < buf_start || ptr_loc > buf_end || end_loc > buf_end) {
return -1;
}
// 使用 0 填充字符串内容
memset(ptr, 0, size);
// 返回成功
return 0;
}
static npy_string_arena NEW_ARENA = {0, 0, NULL};
NPY_NO_EXPORT npy_string_allocator *
NpyString_new_allocator(npy_string_malloc_func m, npy_string_free_func f,
npy_string_realloc_func r)
{
// 分配内存以存储 npy_string_allocator 结构
npy_string_allocator *allocator = m(sizeof(npy_string_allocator));
if (allocator == NULL) {
return NULL;
}
// 分配线程锁
PyThread_type_lock *allocator_lock = PyThread_allocate_lock();
if (allocator_lock == NULL) {
// 如果分配线程锁失败,释放已分配的 allocator 内存,设置错误信息并返回 NULL
f(allocator);
PyErr_SetString(PyExc_MemoryError, "Unable to allocate thread lock");
return NULL;
}
// 初始化 allocator 结构
allocator->malloc = m;
allocator->free = f;
allocator->realloc = r;
// 初始化 arena 为空的 NEW_ARENA
allocator->arena = NEW_ARENA;
// 分配的线程锁赋值给 allocator 的 allocator_lock
allocator->allocator_lock = allocator_lock;
// 返回初始化后的 allocator
return allocator;
}
NPY_NO_EXPORT void
NpyString_free_allocator(npy_string_allocator *allocator)
{
// 获取 allocator 的 free 函数指针
npy_string_free_func f = allocator->free;
// 如果 arena 的 buffer 不为空,释放其内存
if (allocator->arena.buffer != NULL) {
f(allocator->arena.buffer);
}
// 如果 allocator 的 allocator_lock 不为空,释放其内存
if (allocator->allocator_lock != NULL) {
PyThread_free_lock(allocator->allocator_lock);
}
// 释放 allocator 结构自身的内存
f(allocator);
}
/*NUMPY_API
* 获取与 *descr* 相关联的 allocator 的互斥锁。
*
* 必须对此函数返回的 allocator 调用一次 NpyString_release_allocator。
*
* 注意,获取 allocator 互斥锁期间,不应调用需要 GIL 的函数,否则可能导致死锁。
*/
NPY_NO_EXPORT npy_string_allocator *
NpyString_acquire_allocator(const PyArray_StringDTypeObject *descr)
{
// 尝试获取 allocator 的互斥锁,如果不能立即获得,则等待获取
if (!PyThread_acquire_lock(descr->allocator->allocator_lock, NOWAIT_LOCK)) {
PyThread_acquire_lock(descr->allocator->allocator_lock, WAIT_LOCK);
}
// 返回 descr 所关联的 allocator
return descr->allocator;
}
/*NUMPY_API
* Simultaneously acquire the mutexes locking the allocators attached to
* multiple descriptors.
*
* Writes a pointer to the associated allocator in the allocators array for
* each StringDType descriptor in the array. If any of the descriptors are not
* StringDType instances, write NULL to the allocators array for that entry.
*
* *n_descriptors* is the number of descriptors in the descrs array that
* should be examined. Any descriptor after *n_descriptors* elements is
* ignored. A buffer overflow will happen if the *descrs* array does not
* contain n_descriptors elements.
*
* If pointers to the same descriptor are passed multiple times, only acquires
* the allocator mutex once but sets identical allocator pointers appropriately.
*
* The allocator mutexes must be released after this function returns, see
* NpyString_release_allocators.
*
* Note that functions requiring the GIL should not be called while the
* allocator mutex is held, as doing so may cause deadlocks.
*/
NPY_NO_EXPORT void
NpyString_acquire_allocators(size_t n_descriptors,
PyArray_Descr *const descrs[],
npy_string_allocator *allocators[])
{
for (size_t i=0; i<n_descriptors; i++) {
// 检查描述符是否为 StringDType 类型
if (NPY_DTYPE(descrs[i]) != &PyArray_StringDType) {
// 若不是,则分配器指针置为 NULL
allocators[i] = NULL;
continue;
}
int allocators_match = 0;
// 检查之前的描述符是否已经处理过相同的分配器
for (size_t j=0; j<i; j++) {
if (allocators[j] == NULL) {
continue;
}
// 如果找到相同的分配器,则复用之前的分配器指针
if (((PyArray_StringDTypeObject *)descrs[i])->allocator ==
((PyArray_StringDTypeObject *)descrs[j])->allocator)
{
allocators[i] = allocators[j];
allocators_match = 1;
break;
}
}
// 如果未找到相同的分配器,则获取新的分配器,并更新分配器指针
if (!allocators_match) {
allocators[i] = NpyString_acquire_allocator(
(PyArray_StringDTypeObject *)descrs[i]);
}
}
}
/*NUMPY_API
* Release the mutex locking an allocator. This must be called exactly once
* after acquiring the allocator mutex and all operations requiring the
* allocator are done.
*
* If you need to release multiple allocators, see
* NpyString_release_allocators, which can correctly handle releasing the
* allocator once when given several references to the same allocator.
*/
NPY_NO_EXPORT void
NpyString_release_allocator(npy_string_allocator *allocator)
{
// 释放单个分配器的互斥锁
PyThread_release_lock(allocator->allocator_lock);
}
/*NUMPY_API
* Release the mutexes locking N allocators.
*
* *length* is the length of the allocators array. NULL entries are ignored.
*
* If pointers to the same allocator are passed multiple times, only releases
* the allocator mutex once.
*/
NPY_NO_EXPORT void
NpyString_release_allocators(size_t length, npy_string_allocator *allocators[])
{
for (size_t i=0; i<length; i++) {
if (allocators[i] == NULL) {
continue;
}
int matches = 0;
for (size_t j=0; j<i; j++) {
if (allocators[i] == allocators[j]) {
matches = 1;
break;
}
}
if (!matches) {
NpyString_release_allocator(allocators[i]);
}
}
// Helper for allocating strings that will live on the heap or in the arena
// buffer. Determines whether this is a newly allocated array and the string
// should be appended to an existing arena buffer, new data for an existing
// arena string that is being mutated, or new data for an existing short
// string that is being mutated
static char *
heap_or_arena_allocate(npy_string_allocator *allocator,
_npy_static_string_u *to_init_u, size_t size,
int *on_heap)
{
// 获取直接缓冲区的大小和标志位
unsigned char *flags = &to_init_u->direct_buffer.size_and_flags;
if (!(*flags & NPY_STRING_OUTSIDE_ARENA)) {
// 如果标志位中不包含 NPY_STRING_OUTSIDE_ARENA,则表示使用 Arena 分配或重新分配内存。
npy_string_arena *arena = &allocator->arena;
// 获取分配器中的 Arena 对象的引用
if (arena == NULL) {
return NULL;
}
// 如果 Arena 为空,则返回空指针
if (*flags == 0) {
// 如果标志位为 0,表示字符串尚未分配,因此需要添加到现有的 Arena 分配中
char *ret = arena_malloc(arena, allocator->realloc, sizeof(char) * size);
// 在 Arena 中分配大小为 sizeof(char) * size 的内存块
if (size < NPY_MEDIUM_STRING_MAX_SIZE) {
*flags = NPY_STRING_INITIALIZED;
}
else {
*flags = NPY_STRING_INITIALIZED | NPY_STRING_LONG;
}
// 设置标志位,表示字符串已初始化,并根据大小设置是否为长字符串
return ret;
}
// 字符串已经在 Arena 中分配,检查是否仍有空间。
// 大小信息存储在分配的内存块的开头之前。
char *buf = vstring_buffer(arena, to_init_u);
// 获取 Arena 中要初始化的字符串的缓冲区
if (buf == NULL) {
return NULL;
}
// 如果缓冲区为空,则返回空指针
size_t alloc_size;
if (*flags & NPY_STRING_LONG) {
// 对于长字符串,大小信息可能不是内存对齐的,因此使用 memcpy 复制大小信息
size_t *size_loc = (size_t *)((uintptr_t)buf - sizeof(size_t));
memcpy(&alloc_size, size_loc, sizeof(size_t));
}
else {
// 中等长度的字符串大小存储在一个 char 中,因此可以直接访问
alloc_size = (size_t) * (unsigned char *)(buf - 1);
}
if (size <= alloc_size) {
// 如果请求的大小小于等于分配的大小,则表示有足够的空间,直接返回缓冲区
return buf;
}
// 没有足够的空间,需要通过堆分配。
}
// 在堆上分配
*on_heap = 1;
// 设置标志位,表示字符串在堆上分配
*flags = NPY_STRING_INITIALIZED | NPY_STRING_OUTSIDE_ARENA | NPY_STRING_LONG;
// 设置标志位,表示字符串已初始化、在 Arena 外部分配、为长字符串
return allocator->malloc(sizeof(char) * size);
// 使用分配器在堆上分配大小为 sizeof(char) * size 的内存块
static int
heap_or_arena_deallocate(npy_string_allocator *allocator,
_npy_static_string_u *str_u)
{
// 确保字符串不为空
assert (VSTRING_SIZE(str_u) > 0); // should not get here with empty string.
if (STRING_FLAGS(str_u) & NPY_STRING_OUTSIDE_ARENA) {
// 如果字符串存储在堆上(而非内存池中),则需要使用 free() 释放
// 对于堆上的字符串,偏移量是一个原始地址,所以这里的类型转换是安全的。
allocator->free((char *)str_u->vstring.offset);
str_u->vstring.offset = 0;
}
else {
// 字符串存储在内存池中
npy_string_arena *arena = &allocator->arena;
if (arena == NULL) {
return -1;
}
// 调用 arena_free 函数释放内存池中的字符串
if (arena_free(arena, str_u) < 0) {
return -1;
}
}
return 0;
}
// 一个普通的空字符串只是一个大小为 0 的短字符串。但如果一个字符串已经在内存池中初始化,
// 我们只需将 vstring 的大小设置为 0,这样如果字符串再次被重置,我们仍然可以使用内存池。
NPY_NO_EXPORT int
NpyString_pack_empty(npy_packed_static_string *out)
{
_npy_static_string_u *out_u = (_npy_static_string_u *)out;
unsigned char *flags = &out_u->direct_buffer.size_and_flags;
if (*flags & NPY_STRING_OUTSIDE_ARENA) {
// 这也将短字符串的大小设置为 0。
*flags = NPY_STRING_INITIALIZED | NPY_STRING_OUTSIDE_ARENA;
}
else {
// 调用 set_vstring_size 函数将 vstring 的大小设置为 0
set_vstring_size(out_u, 0);
}
return 0;
}
NPY_NO_EXPORT int
NpyString_newemptysize(size_t size, npy_packed_static_string *out,
npy_string_allocator *allocator)
{
if (size == 0) {
return NpyString_pack_empty(out);
}
if (size > NPY_MAX_STRING_SIZE) {
return -1;
}
_npy_static_string_u *out_u = (_npy_static_string_u *)out;
if (size > NPY_SHORT_STRING_MAX_SIZE) {
// 大于 NPY_SHORT_STRING_MAX_SIZE 的字符串存储在堆上或内存池中
int on_heap = 0;
char *buf = heap_or_arena_allocate(allocator, out_u, size, &on_heap);
if (buf == NULL) {
return -1;
}
if (on_heap) {
// 如果存储在堆上,则将偏移量设置为 buf 的地址
out_u->vstring.offset = (size_t)buf;
}
else {
// 如果存储在内存池中,则将偏移量设置为 buf 相对于内存池缓冲区的偏移量
npy_string_arena *arena = &allocator->arena;
if (arena == NULL) {
return -1;
}
out_u->vstring.offset = (size_t)buf - (size_t)arena->buffer;
}
// 调用 set_vstring_size 函数设置 vstring 的大小为 size
set_vstring_size(out_u, size);
}
else {
// 大小不超过 CPU 架构的限制(7 或 15),直接设置短字符串的大小和标志
out_u->direct_buffer.size_and_flags =
NPY_STRING_INITIALIZED | NPY_STRING_OUTSIDE_ARENA | size;
}
return 0;
}
/*NUMPY_API
* 创建一个新的 npy_packed_static_string,将指定的初始化字符串复制到其中
*
* 初始化一个长度为 size 的静态字符串结构,并将 init 指向的字符串复制到其中。
* 如果初始化失败或 size 为 0,则返回 -1;否则返回 0。
*/
NpyString_newsize(const char *init, size_t size,
npy_packed_static_string *to_init,
npy_string_allocator *allocator)
{
// 尝试创建指定大小的空字符串结构,如果失败则返回 -1
if (NpyString_newemptysize(size, to_init, allocator) < 0) {
return -1;
}
// 如果 size 为 0,则直接返回成功
if (size == 0) {
return 0;
}
// 转换 to_init 到 _npy_static_string_u 指针类型
_npy_static_string_u *to_init_u = ((_npy_static_string_u *)to_init);
// 缓冲区指针,根据字符串大小选择合适的缓冲区
char *buf = NULL;
if (size > NPY_SHORT_STRING_MAX_SIZE) {
// 对于大于最大短字符串限制的字符串,从 allocator 的 arena 中获取缓冲区
buf = vstring_buffer(&allocator->arena, to_init_u);
}
else {
// 否则使用直接缓冲区
buf = to_init_u->direct_buffer.buf;
}
// 将 init 指向的字符串复制到 buf 缓冲区中
memcpy(buf, init, size);
return 0;
}
NPY_NO_EXPORT int
NpyString_free(npy_packed_static_string *str, npy_string_allocator *allocator)
{
// 将 str 转换为 _npy_static_string_u 指针类型
_npy_static_string_u *str_u = (_npy_static_string_u *)str;
// 获取直接缓冲区的标志位指针
unsigned char *flags = &str_u->direct_buffer.size_and_flags;
// 无条件地移除指示某些内容缺失的标志位
// 对于这种情况,字符串应该已经被释放,但为了安全起见,我们仍然进行检查
*flags &= ~NPY_STRING_MISSING;
// 如果是短字符串,且字符串大小大于 0
if (is_short_string(str)) {
if (SHORT_STRING_SIZE(str_u) > 0) {
// 清零缓冲区,并设置标志位表示初始化完成的空字符串
memcpy(str_u, &empty_string_u, sizeof(_npy_static_string_u));
*flags = NPY_STRING_OUTSIDE_ARENA | NPY_STRING_INITIALIZED;
}
}
else { // 如果是长字符串
if (VSTRING_SIZE(str_u) > 0) {
// 释放字符串,并将大小设置为 0 表示已释放
if (heap_or_arena_deallocate(allocator, str_u) < 0) {
return -1;
}
set_vstring_size(str_u, 0);
}
}
return 0;
}
/*NUMPY_API
* 将空字符串打包到 npy_packed_static_string 中
*
* 将空字符串打包到 packed_string 中。成功返回 0,失败返回 -1。
*/
NPY_NO_EXPORT int
NpyString_pack_null(npy_string_allocator *allocator,
npy_packed_static_string *packed_string)
{
// 将 packed_string 中的字符串释放
_npy_static_string_u *str_u = (_npy_static_string_u *)packed_string;
if (NpyString_free(packed_string, allocator) < 0) {
return -1;
}
// 保留标志位,因为允许变异,所以需要关联原始分配的元数据
str_u->direct_buffer.size_and_flags |= NPY_STRING_MISSING;
return 0;
}
NPY_NO_EXPORT int
NpyString_dup(const npy_packed_static_string *in,
npy_packed_static_string *out,
npy_string_allocator *in_allocator,
npy_string_allocator *out_allocator)
{
// 如果输入字符串为 NULL,则将输出字符串打包为 NULL
if (NpyString_isnull(in)) {
return NpyString_pack_null(out_allocator, out);
}
// 获取输入字符串的大小
size_t size = NpyString_size(in);
// 如果大小为 0,则将输出字符串打包为空字符串
if (size == 0) {
return NpyString_pack_empty(out);
}
// 省略部分代码...
}
// 如果输入字符串是短字符串,则直接将输入复制到输出,返回 0
if (is_short_string(in)) {
memcpy(out, in, sizeof(_npy_static_string_u));
return 0;
}
// 将输入转换为 _npy_static_string_u 指针类型
_npy_static_string_u *in_u = (_npy_static_string_u *)in;
// 初始化输入缓冲为 NULL
char *in_buf = NULL;
// 获取输入分配器的字符串竞技场
npy_string_arena *arena = &in_allocator->arena;
// 用于标记是否使用了 malloc 进行内存分配
int used_malloc = 0;
// 如果输入和输出的分配器相同且输入不是短字符串
if (in_allocator == out_allocator && !is_short_string(in)) {
// 使用输入分配器分配指定大小的内存
in_buf = in_allocator->malloc(size);
// 将 arena 中的数据复制到输入缓冲区中
memcpy(in_buf, vstring_buffer(arena, in_u), size);
// 标记使用了 malloc 进行内存分配
used_malloc = 1;
}
// 否则直接将 arena 中的数据复制到输入缓冲区中
else {
in_buf = vstring_buffer(arena, in_u);
}
// 调用 NpyString_newsize 函数将输入缓冲区内容转换为指定大小的输出内容,并返回结果
int ret = NpyString_newsize(in_buf, size, out, out_allocator);
// 如果之前使用了 malloc 分配内存,则使用输入分配器释放输入缓冲区内存
if (used_malloc) {
in_allocator->free(in_buf);
}
// 返回转换结果
return ret;
}
NPY_NO_EXPORT int
NpyString_isnull(const npy_packed_static_string *s)
{
return (STRING_FLAGS(s) & NPY_STRING_MISSING) == NPY_STRING_MISSING;
}
NPY_NO_EXPORT int
NpyString_cmp(const npy_static_string *s1, const npy_static_string *s2)
{
size_t minsize = s1->size < s2->size ? s1->size : s2->size;
int cmp = 0;
if (minsize != 0) {
cmp = strncmp(s1->buf, s2->buf, minsize);
}
if (cmp == 0) {
if (s1->size > minsize) {
return 1;
}
if (s2->size > minsize) {
return -1;
}
}
return cmp;
}
NPY_NO_EXPORT size_t
NpyString_size(const npy_packed_static_string *packed_string)
{
if (NpyString_isnull(packed_string)) {
return 0;
}
_npy_static_string_u *string_u = (_npy_static_string_u *)packed_string;
if (is_short_string(packed_string)) {
return string_u->direct_buffer.size_and_flags &
NPY_SHORT_STRING_SIZE_MASK;
}
return VSTRING_SIZE(string_u);
}
/*NUMPY_API
* Pack a string buffer into a npy_packed_static_string
*
* Copy and pack the first *size* entries of the buffer pointed to by *buf*
* into the *packed_string*. Returns 0 on success and -1 on failure.
*/
NPY_NO_EXPORT int
NpyString_pack(npy_string_allocator *allocator,
npy_packed_static_string *packed_string, const char *buf,
size_t size)
{
if (NpyString_free(packed_string, allocator) < 0) {
return -1;
}
return NpyString_newsize(buf, size, packed_string, allocator);
}
NPY_NO_EXPORT int
NpyString_share_memory(const npy_packed_static_string *s1, npy_string_allocator *a1,
const npy_packed_static_string *s2, npy_string_allocator *a2) {
if (a1 != a2 ||
is_short_string(s1) || is_short_string(s2) ||
NpyString_isnull(s1) || NpyString_isnull(s2)) {
return 0;
}
_npy_static_string_u *s1_u = (_npy_static_string_u *)s1;
_npy_static_string_u *s2_u = (_npy_static_string_u *)s2;
if (vstring_buffer(&a1->arena, s1_u) == vstring_buffer(&a2->arena, s2_u))
{
return 1;
}
return 0;
}
.\numpy\numpy\_core\src\multiarray\stringdtype\static_string.h
extern "C" {
// some types used by this header are defined in ndarraytypes.h
// 定义最大字符串长度,使用小字符串优化时需要保留一个字节用于标志
// 分配器函数的类型定义
typedef void *(*npy_string_malloc_func)(size_t size);
typedef void (*npy_string_free_func)(void *ptr);
typedef void *(*npy_string_realloc_func)(void *ptr, size_t size);
// 使用这些函数创建和销毁字符串分配器。通常用户不会直接使用这些函数,
// 而是使用已经附加到dtype实例的分配器
NPY_NO_EXPORT npy_string_allocator *
NpyString_new_allocator(npy_string_malloc_func m, npy_string_free_func f,
npy_string_realloc_func r);
// 释放内部缓冲区和分配器本身
NPY_NO_EXPORT void
NpyString_free_allocator(npy_string_allocator *allocator);
// 为*to_init*分配新的缓冲区,*to_init*在调用此函数之前必须设置为NULL,
// 将新分配的缓冲区填充为*init*中前*size*个条目的复制内容,*init*必须是有效并已初始化的。
// 在现有字符串上调用NpyString_free或将NPY_EMPTY_STRING的内容复制到*to_init*,
// 即可初始化它。不检查*to_init*是否为NULL或内部缓冲区是否为非NULL,
// 如果将此函数传递给未初始化的结构体指针、NULL指针或现有的堆分配字符串,
// 可能会导致未定义行为或内存泄漏。如果分配字符串会超过允许的最大字符串大小或耗尽可用内存,则返回-1。
// 成功时返回0。
NPY_NO_EXPORT int
NpyString_newsize(const char *init, size_t size,
npy_packed_static_string *to_init,
npy_string_allocator *allocator);
// 清空压缩字符串并释放任何堆分配的数据。对于区域分配的数据,检查数据是否在区域内,
// 如果不在则返回-1。成功时返回0。
NPY_NO_EXPORT int
NpyString_free(npy_packed_static_string *str, npy_string_allocator *allocator);
// 将*in*的内容复制到*out*中。为*out*分配一个新的字符串缓冲区,
// 如果*out*指向现有字符串,则必须在调用此函数之前调用NpyString_free。
// 如果malloc失败则返回-1。成功时返回0。
NPY_NO_EXPORT int
NpyString_dup(const npy_packed_static_string *in,
npy_packed_static_string *out,
npy_string_allocator *in_allocator,
npy_string_allocator *out_allocator);
// 为*out*分配一个新的字符串缓冲区,以存储*size*字节的文本。
// 不执行任何初始化,调用者必须
注释:
// 初始化一个空字符串缓冲区。在调用该函数之前,调用 NpyString_free(*to_init*) 可以释放已有的字符串,
// 或者使用 NPY_EMPTY_STRING 初始化一个新字符串。不检查 *to_init* 是否已经初始化或内部缓冲区是否非空。
// 如果将 NULL 指针传递给该函数或传递一个已在堆上分配的字符串,可能会导致未定义的行为或内存泄漏。
// 返回值:成功返回 0。如果分配字符串会超过最大允许的字符串大小或耗尽可用内存,返回 -1。
NPY_NO_EXPORT int
NpyString_newemptysize(size_t size, npy_packed_static_string *out,
npy_string_allocator *allocator);
// 判断 *in* 是否为 null 字符串(例如 NA 对象)。
// 返回值:-1 表示 *in* 无法解包;1 表示 *in* 是 null 字符串;0 表示不是 null 字符串。
NPY_NO_EXPORT int
NpyString_isnull(const npy_packed_static_string *in);
// 比较两个字符串。其语义与使用 strcmp 比较以 null 结尾的 C 字符串 *s1* 和 *s2* 的内容相同。
NPY_NO_EXPORT int
NpyString_cmp(const npy_static_string *s1, const npy_static_string *s2);
// 返回打包字符串中字符串数据的大小。在只需要字符串大小而不需要确定是否为 null 或解包字符串的情况下很有用。
NPY_NO_EXPORT size_t
NpyString_size(const npy_packed_static_string *packed_string);
// 检查两个打包的字符串是否共享内存。
// 返回值:0 表示不共享内存;非零表示共享内存。
NPY_NO_EXPORT int
NpyString_share_memory(const npy_packed_static_string *s1, npy_string_allocator *a1,
const npy_packed_static_string *s2, npy_string_allocator *a2);
}
.\numpy\numpy\_core\src\multiarray\stringdtype\utf8_utils.c
// 导入 Python.h 头文件,包含 Python C API
// 定义 NPY_NO_DEPRECATED_API 为 NPY_API_VERSION,避免使用废弃的 API
// 定义 _MULTIARRAYMODULE 宏,用于多维数组模块
// 定义 _UMATHMODULE 宏,用于通用数学函数模块
// 包含 numpy/ndarraytypes.h 头文件,定义了 ndarray 的数据类型和宏
// 包含 utf8_utils.h 头文件,提供 UTF-8 编码的工具函数
// 给定 UTF-8 字节 *c*,将 *code* 设置为对应的 unicode 码点,返回字符所占字节数。
// 不进行验证或错误检查:假定 *c* 是有效的 UTF-8 编码
NPY_NO_EXPORT size_t
utf8_char_to_ucs4_code(const unsigned char *c, Py_UCS4 *code)
{
// 如果第一个字节的最高位为 0,则为 7 位 ASCII 码
if (c[0] <= 0x7F) {
// 0zzzzzzz -> 0zzzzzzz
*code = (Py_UCS4)(c[0]);
return 1;
}
// 如果第一个字节的最高两位为 110,则为 2 字节 UTF-8 序列
else if (c[0] <= 0xDF) {
// 110yyyyy 10zzzzzz -> 00000yyy yyzzzzzz
*code = (Py_UCS4)(((c[0] << 6) + c[1]) - ((0xC0 << 6) + 0x80));
return 2;
}
// 如果第一个字节的最高三位为 1110,则为 3 字节 UTF-8 序列
else if (c[0] <= 0xEF) {
// 1110xxxx 10yyyyyy 10zzzzzz -> xxxxyyyy yyzzzzzz
*code = (Py_UCS4)(((c[0] << 12) + (c[1] << 6) + c[2]) -
((0xE0 << 12) + (0x80 << 6) + 0x80));
return 3;
}
// 如果第一个字节的最高四位为 11110,则为 4 字节 UTF-8 序列
else {
// 11110www 10xxxxxx 10yyyyyy 10zzzzzz -> 000wwwxx xxxxyyyy yyzzzzzz
*code = (Py_UCS4)(((c[0] << 18) + (c[1] << 12) + (c[2] << 6) + c[3]) -
((0xF0 << 18) + (0x80 << 12) + (0x80 << 6) + 0x80));
return 4;
}
}
// 从 *c* 开始向前查找有效 UTF-8 字符的起始位置,*nchar* 是要查找的字符数
NPY_NO_EXPORT const unsigned char*
find_previous_utf8_character(const unsigned char *c, size_t nchar)
{
while (nchar > 0) {
do
{
// 假设 UTF-8 格式正确,不检查是否超出字符串开始位置
c--;
// UTF-8 字符的第一个字节要么最高位为 0,要么最高两位都为 1
} while ((*c & 0xC0) == 0x80);
nchar--;
}
return c;
}
// 返回 UTF-8 字符 *c* 所需的字节数
NPY_NO_EXPORT int
num_bytes_for_utf8_character(const unsigned char *c) {
if (c[0] <= 0x7F) {
return 1;
}
else if (c[0] <= 0xDF) {
return 2;
}
else if (c[0] <= 0xEF) {
return 3;
}
return 4;
}
// 返回给定 unicode 码点 *code* 在 UTF-8 编码下所需的字节数
NPY_NO_EXPORT int
num_utf8_bytes_for_codepoint(uint32_t code)
{
if (code <= 0x7F) {
return 1;
}
else if (code <= 0x07FF) {
return 2;
}
else if (code <= 0xFFFF) {
if ((code >= 0xD800) && (code <= 0xDFFF)) {
// 代理对在 UCS4 中无效
return -1;
}
return 3;
}
else if (code <= 0x10FFFF) {
return 4;
}
else {
// 码点超出有效的 Unicode 范围
return -1;
}
}
// 查找存储由 *codepoints* 表示的 UTF-8 编码字符串所需的字节数。
// *codepoints* 数组的长度为 *max_length*,但可能以空码点填充。
// *num_codepoints* 是不包括尾部空码点的码点数。
// 成功时返回 0,当发现无效码点时返回 -1。
NPY_NO_EXPORT int
// 计算给定 UCS4 编码点数组的 UTF-8 编码所需的字节数和非空编码点数量
utf8_size(const Py_UCS4 *codepoints, long max_length, size_t *num_codepoints,
size_t *utf8_bytes)
{
// 初始化 UCS4 编码点数组的长度为最大长度
size_t ucs4len = max_length;
// 去除末尾的空编码点,更新 ucs4len 为非空编码点的数量
while (ucs4len > 0 && codepoints[ucs4len - 1] == 0) {
ucs4len--;
}
// ucs4len 现在是非尾部空编码点的数量。
// 初始化 UTF-8 编码所需的总字节数
size_t num_bytes = 0;
// 遍历 UCS4 编码点数组,计算每个编码点的 UTF-8 编码字节数,并累加到 num_bytes 中
for (size_t i = 0; i < ucs4len; i++) {
Py_UCS4 code = codepoints[i];
// 调用函数获取当前编码点的 UTF-8 编码字节数
int codepoint_bytes = num_utf8_bytes_for_codepoint((uint32_t)code);
// 如果编码点无效,返回错误标志 -1
if (codepoint_bytes == -1) {
return -1;
}
// 累加当前编码点的 UTF-8 编码字节数到 num_bytes
num_bytes += codepoint_bytes;
}
// 将计算得到的非空编码点数量和 UTF-8 编码总字节数分别存入传入的指针中
*num_codepoints = ucs4len;
*utf8_bytes = num_bytes;
// 返回成功标志 0
return 0;
}
// 将 UCS4 编码点 *code* 转换为 UTF-8 字符数组 *c*,假设 *c* 是一个以 0 填充的 4 字节数组,
// *code* 是一个有效的编码点且不进行任何错误检查!返回 UTF-8 字符的字节数。
NPY_NO_EXPORT size_t
ucs4_code_to_utf8_char(Py_UCS4 code, char *c)
{
// 如果编码点在 ASCII 范围内,直接转换为单字节 UTF-8 字符
if (code <= 0x7F) {
// 0zzzzzzz -> 0zzzzzzz
c[0] = (char)code;
return 1;
}
// 如果编码点在 0x80 到 0x07FF 范围内,转换为两字节 UTF-8 字符
else if (code <= 0x07FF) {
// 00000yyy yyzzzzzz -> 110yyyyy 10zzzzzz
c[0] = (0xC0 | (code >> 6));
c[1] = (0x80 | (code & 0x3F));
return 2;
}
// 如果编码点在 0x0800 到 0xFFFF 范围内,转换为三字节 UTF-8 字符
else if (code <= 0xFFFF) {
// xxxxyyyy yyzzzzzz -> 110yyyyy 10zzzzzz
c[0] = (0xe0 | (code >> 12));
c[1] = (0x80 | ((code >> 6) & 0x3f));
c[2] = (0x80 | (code & 0x3f));
return 3;
}
// 对于其他编码点,转换为四字节 UTF-8 字符
else {
// 00wwwxx xxxxyyyy yyzzzzzz -> 11110www 10xxxxxx 10yyyyyy 10zzzzzz
c[0] = (0xf0 | (code >> 18));
c[1] = (0x80 | ((code >> 12) & 0x3f));
c[2] = (0x80 | ((code >> 6) & 0x3f));
c[3] = (0x80 | (code & 0x3f));
return 4;
}
}
/*******************************************************************************/
// 以下内容是 Bjoern Hoerhmann 的 DFA UTF-8 验证器的拷贝
// 许可证:MIT
// 版权所有 (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
//
// 在遵守以下条件的情况下,特此免费授权给获取本软件及相关文档文件(以下简称“软件”)
// 的任何人,可以无限制地处理本软件,包括但不限于使用、复制、修改、合并、出版、
// 发行、再授权和/或销售本软件的副本,以及允许接收本软件的人这样做,前提是:
//
// 上述版权声明和本许可声明应包含在所有副本或实质性部分的本软件中。
// 本软件按“原样”提供,不提供任何形式的明示或暗示保证,包括但不限于对适销性、特定
// 目的的适用性和非侵权性的保证。在任何情况下,作者或版权持有人均不承担任何索赔、
// 损害赔偿或其他责任。
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//
// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
//
// in principle could use something like simdutf to accelerate this
// 定义 UTF-8 解析器的状态常量
// UTF-8 解析器的状态转换表
static const uint8_t utf8d[] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
};
// UTF-8 解码函数,根据当前状态和输入字节解析出一个 Unicode 编码点
static uint32_t inline
utf8_decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
uint32_t type = utf8d[byte];
*codep = (*state != UTF8_ACCEPT) ?
(byte & 0x3fu) | (*codep << 6) :
(0xff >> type) & (byte);
*state = utf8d[256 + *state*16 + type];
return *state;
}
/*******************************************************************************/
// 计算存储 UTF-8 编码的 UTF-32 编码字符串所需的字节大小
// **s** 指向的 UTF-32 编码字符串的最大长度为 **max_bytes**。
NPY_NO_EXPORT Py_ssize_t
utf8_buffer_size(const uint8_t *s, size_t max_bytes)
{
uint32_t codepoint;
uint32_t state = 0;
size_t num_bytes = 0;
Py_ssize_t encoded_size_in_bytes = 0;
// 忽略末尾的空字节
while (max_bytes > 0 && s[max_bytes - 1] == 0) {
max_bytes--;
}
if (max_bytes == 0) {
return 0;
}
// 遍历输入的 UTF-32 编码字符串
for (; num_bytes < max_bytes; ++s)
{
// 解析每个字节并更新解析状态和 Unicode 编码点
utf8_decode(&state, &codepoint, *s);
if (state == UTF8_REJECT)
{
return -1; // 解析失败,返回错误码
}
else if(state == UTF8_ACCEPT)
{
// 当解析状态为接受状态时,计算该 Unicode 编码点的 UTF-8 编码字节数
encoded_size_in_bytes += num_utf8_bytes_for_codepoint(codepoint);
}
num_bytes += 1;
}
// 如果最终状态不是接受状态,则表示输入不是一个有效的 UTF-8 字符串
if (state != UTF8_ACCEPT) {
return -1;
}
return encoded_size_in_bytes; // 返回计算出的 UTF-8 编码字节数
}
// 计算以 UTF-8 编码的字节串中的代码点数量
num_codepoints_for_utf8_bytes(const unsigned char *s, size_t *num_codepoints, size_t max_bytes)
{
uint32_t codepoint; // 存储解码后的代码点
uint32_t state = 0; // UTF-8 解码状态
size_t num_bytes = 0; // 已处理的字节数
*num_codepoints = 0; // 初始化代码点数量为0
// 忽略末尾的空字节
while (max_bytes > 0 && s[max_bytes - 1] == 0) {
max_bytes--;
}
if (max_bytes == 0) {
return UTF8_ACCEPT; // 如果最大字节数为0,直接返回 UTF8_ACCEPT
}
// 遍历输入的字节序列
for (; num_bytes < max_bytes; ++s)
{
utf8_decode(&state, &codepoint, *s); // 解码一个 UTF-8 字符
if (state == UTF8_REJECT)
{
return state; // 如果解码失败,返回 UTF8_REJECT
}
else if(state == UTF8_ACCEPT)
{
*num_codepoints += 1; // 如果成功解码一个代码点,增加代码点计数
}
num_bytes += 1; // 增加已处理的字节数
}
return state != UTF8_ACCEPT; // 返回是否最终处于接受状态
}
// 查找指定起始和结束索引对应的位置
NPY_NO_EXPORT void
find_start_end_locs(char* buf, size_t buffer_size, npy_int64 start_index, npy_int64 end_index,
char **start_loc, char **end_loc) {
size_t bytes_consumed = 0; // 已消耗的字节数
size_t num_codepoints = 0; // 当前已处理的代码点数量
if (num_codepoints == (size_t) start_index) {
*start_loc = buf; // 如果当前代码点数量等于起始索引,设置起始位置
}
if (num_codepoints == (size_t) end_index) {
*end_loc = buf; // 如果当前代码点数量等于结束索引,设置结束位置
}
// 遍历缓冲区直到达到结束索引或超过缓冲区大小
while (bytes_consumed < buffer_size && num_codepoints < (size_t) end_index) {
size_t num_bytes = num_bytes_for_utf8_character((const unsigned char*)buf); // 获取当前 UTF-8 字符所占字节数
num_codepoints += 1; // 增加已处理的代码点数量
bytes_consumed += num_bytes; // 增加已消耗的字节数
buf += num_bytes; // 移动缓冲区指针到下一个字符的起始位置
if (num_codepoints == (size_t) start_index) {
*start_loc = buf; // 如果当前代码点数量等于起始索引,设置起始位置
}
if (num_codepoints == (size_t) end_index) {
*end_loc = buf; // 如果当前代码点数量等于结束索引,设置结束位置
}
}
assert(start_loc != NULL); // 断言起始位置非空
assert(end_loc != NULL); // 断言结束位置非空
}
// 计算从给定起始位置到搜索字节偏移量处的 UTF-8 字符索引
NPY_NO_EXPORT size_t
utf8_character_index(
const char* start_loc, size_t start_byte_offset, size_t start_index,
size_t search_byte_offset, size_t buffer_size)
{
size_t bytes_consumed = 0; // 已消耗的字节数
size_t cur_index = start_index; // 当前代码点索引,初始为起始索引
while (bytes_consumed < buffer_size && bytes_consumed < search_byte_offset) {
size_t num_bytes = num_bytes_for_utf8_character((const unsigned char*)start_loc); // 获取当前 UTF-8 字符所占字节数
cur_index += 1; // 增加当前代码点索引
bytes_consumed += num_bytes; // 增加已消耗的字节数
start_loc += num_bytes; // 移动起始位置到下一个字符的起始位置
}
return cur_index - start_index; // 返回起始索引到搜索偏移量处的代码点数量
}
.\numpy\numpy\_core\src\multiarray\stringdtype\utf8_utils.h
extern "C" {
// 声明一个内部函数,将一个 UTF-8 字符转换为 UCS4 编码,并返回转换后的代码点
NPY_NO_EXPORT size_t
utf8_char_to_ucs4_code(const unsigned char *c, Py_UCS4 *code);
// 声明一个内部函数,返回给定 UTF-8 字符的字节数
NPY_NO_EXPORT int
num_bytes_for_utf8_character(const unsigned char *c);
// 声明一个内部函数,查找给定位置之前的一个 UTF-8 字符的起始位置
NPY_NO_EXPORT const unsigned char*
find_previous_utf8_character(const unsigned char *c, size_t nchar);
// 声明一个内部函数,返回给定代码点所需的 UTF-8 字节数
NPY_NO_EXPORT int
num_utf8_bytes_for_codepoint(uint32_t code);
// 声明一个内部函数,返回给定 UTF-8 字节序列中的代码点数量
NPY_NO_EXPORT int
num_codepoints_for_utf8_bytes(const unsigned char *s, size_t *num_codepoints, size_t max_bytes);
// 声明一个内部函数,计算给定 UCS4 编码序列转换为 UTF-8 编码所需的字节数和代码点数量
NPY_NO_EXPORT int
utf8_size(const Py_UCS4 *codepoints, long max_length, size_t *num_codepoints,
size_t *utf8_bytes);
// 声明一个内部函数,将给定 UCS4 编码转换为 UTF-8 字符,并返回所需的字节数
NPY_NO_EXPORT size_t
ucs4_code_to_utf8_char(Py_UCS4 code, char *c);
// 声明一个内部函数,计算给定 UTF-8 编码序列的缓冲区大小
NPY_NO_EXPORT Py_ssize_t
utf8_buffer_size(const uint8_t *s, size_t max_bytes);
// 声明一个内部函数,查找给定缓冲区中指定范围的起始和结束位置
NPY_NO_EXPORT void
find_start_end_locs(char* buf, size_t buffer_size, npy_int64 start_index, npy_int64 end_index,
char **start_loc, char **end_loc);
// 声明一个内部函数,计算给定缓冲区中指定位置的 UTF-8 字符的索引
NPY_NO_EXPORT size_t
utf8_character_index(
const char* start_loc, size_t start_byte_offset, size_t start_index,
size_t search_byte_offset, size_t buffer_size);
}
这些注释描述了每个函数声明的作用和功能,按照要求解释了每行代码的含义。
.\numpy\numpy\_core\src\multiarray\temp_elide.c
/*
* 定义宏以确保使用最新的 NumPy API 版本,并声明 _MULTIARRAYMODULE 宏
*/
/*
* 清除 PY_SSIZE_T_CLEAN 宏定义,确保 Python.h 之前不会定义 ssize_t
* 包含 Python.h 头文件,这是 Python C API 的主头文件
*/
/*
* 包含 NumPy 的配置信息头文件和数组对象头文件
* npy_config.h 包含了 NumPy 库的配置信息
* numpy/arrayobject.h 包含了数组对象的定义和操作函数声明
*/
/*
* 定义宏 NPY_NUMBER_MAX,用于返回两个数中的较大值
* 宏 ARRAY_SIZE 用于计算数组的元素个数
*/
/*
* 以下是用于尝试在 Python 表达式中避免或简化临时变量的函数
* 以便将某些操作转换为原地操作,避免创建不必要的临时变量
* 详细解释见注释块中的英文说明
*/
/*
* 如果定义了 HAVE_BACKTRACE 和 HAVE_DLFCN_H,并且未定义 PYPY_VERSION
* 则包含 feature_detection_misc.h 头文件
*/
/*
* 定义 NPY_ELIDE_DEBUG 宏为 0,不打印简化的操作
* 定义 NPY_MAX_STACKSIZE 宏为 10,指定最大的堆栈大小
*/
/* TODO can pep523 be used to somehow? */
/*
* 以上代码段主要用于处理 Python 和 NumPy C 扩展中的一些宏定义和功能声明,
* 包括 API 版本、数组操作宏、操作优化的功能说明等。
*/
/*
* 根据 Python 中的调用帧默认评估函数名称定义常量 "_PyEval_EvalFrameDefault"
* 这是 C 宏的定义,用于在代码中引用这个函数名。
*/
/*
* 在以下条件下,回溯开销小于通过原地操作获得的速度增益的启发性数组大小,这取决于被检查的堆栈深度。
* 通过对 10 个堆栈的测量表明,大约在 100KiB 时开始变得有价值,但为了保守起见,设置更高的值,接近 L2 缓存的溢出处。
*/
/*
* 在调试模式下总是省略,但跳过标量因为它们可以在原地操作期间转换为 0 维数组。
*/
/*
* 在表中进行指针的线性搜索,指针的数量通常很少,但如果可以测量到性能影响,这可以转换为二分搜索。
*/
static int
find_addr(void * addresses[], npy_intp naddr, void * addr)
{
npy_intp j;
for (j = 0; j < naddr; j++) {
if (addr == addresses[j]) {
return 1;
}
}
return 0;
}
/*
* 检查调用者,获取 multiarray 和 Python 的基地址,并检查回溯是否仅在这些库中调用 dladdr,
* 仅在初始的 multiarray 堆栈之后,所有内容都在 Python 内部时,我们可以省略,因为没有 C-API 用户可能会搞乱引用计数。
* 仅在找到 Python 帧评估函数之前检查,大约每个堆栈大小为 10 时的开销为 10 微秒。
*
* TODO: 有些调用经过 umath 中的 scalarmath,但我们无法从 multiarraymodule 中获取它的基地址,因为它没有与其链接。
*/
static int
check_callers(int * cannot)
{
NPY_TLS static int init = 0;
/*
* 测量 DSO(动态共享对象)对象的内存起始和结束,如果一个地址位于这些边界内部,那么它属于该库的一部分,
* 因此我们不需要对其调用 dladdr(假设是线性内存)。
*/
NPY_TLS static void * pos_python_start;
NPY_TLS static void * pos_python_end;
NPY_TLS static void * pos_ma_start;
NPY_TLS static void * pos_ma_end;
/* 存储已知地址以节省 dladdr 调用 */
NPY_TLS static void * py_addr[64];
NPY_TLS static void * pyeval_addr[64];
NPY_TLS static npy_intp n_py_addr = 0;
NPY_TLS static npy_intp n_pyeval = 0;
void *buffer[NPY_MAX_STACKSIZE];
int i, nptrs;
int ok = 0;
/* 无法确定调用者 */
if (init == -1) {
*cannot = 1;
return 0;
}
nptrs = backtrace(buffer, NPY_MAX_STACKSIZE);
if (nptrs == 0) {
/* 完全失败,禁用省略 */
init = -1;
*cannot = 1;
return 0;
}
/* 设置 DSO 基地址,结束后更新 */
// 检查初始化是否已经完成,如果未完成则执行以下逻辑
if (NPY_UNLIKELY(init == 0)) {
Dl_info info;
/* get python base address */
// 获取 PyNumber_Or 函数的基地址信息
if (dladdr(&PyNumber_Or, &info)) {
// 设置 Python 起始和结束地址为获取到的基地址
pos_python_start = info.dli_fbase;
pos_python_end = info.dli_fbase;
}
else {
// 如果获取失败,标记初始化为失败状态并返回 0
init = -1;
return 0;
}
/* get multiarray base address */
// 获取 PyArray_INCREF 函数的基地址信息
if (dladdr(&PyArray_INCREF, &info)) {
// 设置多维数组起始和结束地址为获取到的基地址
pos_ma_start = info.dli_fbase;
pos_ma_end = info.dli_fbase;
}
else {
// 如果获取失败,标记初始化为失败状态并返回 0
init = -1;
return 0;
}
// 初始化成功标志置为 1
init = 1;
}
/* loop over callstack addresses to check if they leave numpy or cpython */
// 循环遍历调用堆栈地址,检查是否离开了 numpy 或者 cpython
for (i = 0; i < nptrs; i++) {
Dl_info info;
int in_python = 0;
int in_multiarray = 0;
// 如果调试级别大于等于2,使用dladdr函数获取缓冲区中每个地址对应的符号信息,并打印到标准输出
dladdr(buffer[i], &info);
printf("%s(%p) %s(%p)\n", info.dli_fname, info.dli_fbase,
info.dli_sname, info.dli_saddr);
/* check stored DSO boundaries first */
// 首先检查存储的动态共享对象(DSO)边界
if (buffer[i] >= pos_python_start && buffer[i] <= pos_python_end) {
// 如果当前缓冲区中的地址在Python模块的范围内,设置in_python为1
in_python = 1;
}
else if (buffer[i] >= pos_ma_start && buffer[i] <= pos_ma_end) {
// 如果当前缓冲区中的地址在多维数组模块的范围内,设置in_multiarray为1
in_multiarray = 1;
}
/* update DSO boundaries via dladdr if necessary */
// 如果不在Python模块和多维数组模块内,通过dladdr更新DSO边界
if (!in_python && !in_multiarray) {
// 如果dladdr无法获取地址信息,初始化为-1,标记为不可用,并跳出循环
if (dladdr(buffer[i], &info) == 0) {
init = -1;
ok = 0;
break;
}
/* update DSO end */
// 更新DSO的结束地址
if (info.dli_fbase == pos_python_start) {
// 如果地址信息中的基地址与Python模块的起始地址相同,更新Python模块的结束地址
pos_python_end = NPY_NUMBER_MAX(buffer[i], pos_python_end);
in_python = 1;
}
else if (info.dli_fbase == pos_ma_start) {
// 如果地址信息中的基地址与多维数组模块的起始地址相同,更新多维数组模块的结束地址
pos_ma_end = NPY_NUMBER_MAX(buffer[i], pos_ma_end);
in_multiarray = 1;
}
}
/* no longer in ok libraries and not reached PyEval -> no elide */
// 如果既不在Python模块也不在多维数组模块内,标记为不可用,并跳出循环
if (!in_python && !in_multiarray) {
ok = 0;
break;
}
/* in python check if the frame eval function was reached */
// 在Python模块内,检查是否达到了帧评估函数
if (in_python) {
/* if reached eval we are done */
// 如果达到了评估函数,标记为可用,并跳出循环
if (find_addr(pyeval_addr, n_pyeval, buffer[i])) {
ok = 1;
break;
}
/*
* check if its some other function, use pointer lookup table to
* save expensive dladdr calls
*/
// 检查是否为其他函数,使用指针查找表避免昂贵的dladdr调用
if (find_addr(py_addr, n_py_addr, buffer[i])) {
continue;
}
/* new python address, check for PyEvalFrame */
// 新的Python地址,检查是否为PyEvalFrame函数
if (dladdr(buffer[i], &info) == 0) {
init = -1;
ok = 0;
break;
}
if (info.dli_sname &&
strcmp(info.dli_sname, PYFRAMEEVAL_FUNC) == 0) {
// 如果地址信息中的符号名与PYFRAMEEVAL_FUNC相同,将地址存储到pyeval_addr数组中,标记为可用,并跳出循环
if (n_pyeval < (npy_intp)ARRAY_SIZE(pyeval_addr)) {
pyeval_addr[n_pyeval++] = buffer[i];
}
ok = 1;
break;
}
else if (n_py_addr < (npy_intp)ARRAY_SIZE(py_addr)) {
// 否则将地址存储到py_addr数组中,以避免再次进行dladdr调用
py_addr[n_py_addr++] = buffer[i];
}
}
}
/* all stacks after numpy are from python, we can elide */
// 如果ok为真,则所有numpy之后的堆栈都来自Python,可以删除
if (ok) {
*cannot = 0;
return 1;
}
else {
// 如果不允许删除(NPY_ELIDE_DEBUG不为0),输出信息提示不可删除
puts("cannot elide due to c-api usage");
*cannot = 1;
return 0;
}
}
/*
* 检查在 "alhs @op@ orhs" 中,如果 alhs 是一个临时对象(refcnt == 1),
* 则可以进行原地操作而不是创建一个新的临时对象。
* 如果即使交换了参数也无法进行原地操作,则设置 cannot 为 true。
*/
static int
can_elide_temp(PyObject *olhs, PyObject *orhs, int *cannot)
{
/*
* 要成为候选对象,数组需要满足以下条件:
* - 引用计数为 1
* - 是一个精确的基本类型数组
* - 拥有自己的数据
* - 可写
* - 不是写入时复制的数组
* - 数据大小大于阈值 NPY_MIN_ELIDE_BYTES
*/
PyArrayObject *alhs = (PyArrayObject *)olhs;
if (Py_REFCNT(olhs) != 1 || !PyArray_CheckExact(olhs) ||
!PyArray_ISNUMBER(alhs) ||
!PyArray_CHKFLAGS(alhs, NPY_ARRAY_OWNDATA) ||
!PyArray_ISWRITEABLE(alhs) ||
PyArray_CHKFLAGS(alhs, NPY_ARRAY_WRITEBACKIFCOPY) ||
PyArray_NBYTES(alhs) < NPY_MIN_ELIDE_BYTES) {
return 0;
}
if (PyArray_CheckExact(orhs) ||
PyArray_CheckAnyScalar(orhs)) {
PyArrayObject * arhs;
/* 从右操作数创建数组 */
Py_INCREF(orhs);
arhs = (PyArrayObject *)PyArray_EnsureArray(orhs);
if (arhs == NULL) {
return 0;
}
/*
* 如果右操作数不是标量,维度必须匹配
* TODO: 可以考虑在相同类型下进行广播
*/
if (!(PyArray_NDIM(arhs) == 0 ||
(PyArray_NDIM(arhs) == PyArray_NDIM(alhs) &&
PyArray_CompareLists(PyArray_DIMS(alhs), PyArray_DIMS(arhs),
PyArray_NDIM(arhs))))) {
Py_DECREF(arhs);
return 0;
}
/* 必须能够安全地转换(检查右操作数中的标量值) */
if (PyArray_CanCastArrayTo(arhs, PyArray_DESCR(alhs),
NPY_SAFE_CASTING)) {
Py_DECREF(arhs);
return check_callers(cannot);
}
Py_DECREF(arhs);
}
return 0;
}
/*
* 尝试消除二元操作中的临时对象,如果 commutative 为 true,则尝试交换参数
*/
NPY_NO_EXPORT int
try_binary_elide(PyObject * m1, PyObject * m2,
PyObject * (inplace_op)(PyArrayObject * m1, PyObject * m2),
PyObject ** res, int commutative)
{
/* 当不能独立于参数顺序进行消除时设置为 true */
int cannot = 0;
if (can_elide_temp(m1, m2, &cannot)) {
*res = inplace_op((PyArrayObject *)m1, m2);
puts("elided temporary in binary op");
return 1;
}
else if (commutative && !cannot) {
if (can_elide_temp(m2, m1, &cannot)) {
*res = inplace_op((PyArrayObject *)m2, m1);
puts("elided temporary in commutative binary op");
return 1;
}
}
*res = NULL;
return 0;
}
/* 尝试消除一元操作中的临时对象 */
NPY_NO_EXPORT int
can_elide_temp_unary(PyArrayObject * m1)
{
int cannot;
if (Py_REFCNT(m1) != 1 || !PyArray_CheckExact(m1) ||
!PyArray_ISNUMBER(m1) ||
!PyArray_CHKFLAGS(m1, NPY_ARRAY_OWNDATA) ||
!PyArray_ISWRITEABLE(m1) ||
PyArray_NBYTES(m1) < NPY_MIN_ELIDE_BYTES) {
return 0;
}
if (check_callers(&cannot)) {
puts("elided temporary in unary op");
// 如果 NPY_ELIDE_DEBUG 宏不为 0,则打印调试信息到标准输出
return 1;
}
else {
// 否则返回 0
return 0;
}
}
NPY_NO_EXPORT int
can_elide_temp_unary(PyArrayObject * m1)
{
// 返回 0,表示不支持的解释器或者缺少回溯功能
return 0;
}
NPY_NO_EXPORT int
try_binary_elide(PyArrayObject * m1, PyObject * m2,
PyObject * (inplace_op)(PyArrayObject * m1, PyObject * m2),
PyObject ** res, int commutative)
{
// 将 res 指针设为 NULL
*res = NULL;
// 返回 0,表示未成功进行二元操作的优化
return 0;
}
.\numpy\numpy\_core\src\multiarray\temp_elide.h
// 如果未定义 NUMPY_CORE_SRC_MULTIARRAY_TEMP_ELIDE_H_ 宏,则执行以下内容
// 定义 NUMPY_CORE_SRC_MULTIARRAY_TEMP_ELIDE_H_ 宏
// 定义 NPY_NO_DEPRECATED_API 宏为当前 NPY_API_VERSION,表示不使用已弃用的 API
// 定义 _MULTIARRAYMODULE 宏,用于某些条件编译
// 包含 numpy/ndarraytypes.h 头文件,提供对 NumPy 数组类型的支持
NPY_NO_EXPORT int
// NPY_NO_EXPORT 指示编译器不导出此函数符号
can_elide_temp_unary(PyArrayObject * m1);
// 函数声明:判断是否可以省略一元操作的临时变量
NPY_NO_EXPORT int
// NPY_NO_EXPORT 指示编译器不导出此函数符号
try_binary_elide(PyObject * m1, PyObject * m2,
PyObject * (inplace_op)(PyArrayObject * m1, PyObject * m2),
PyObject ** res, int commutative);
// 函数声明:尝试省略二元操作的临时变量,支持就地操作和交换性操作
// 结束条件编译指令,确保头文件内容只在未定义 NUMPY_CORE_SRC_MULTIARRAY_TEMP_ELIDE_H_ 宏时有效
.\numpy\numpy\_core\src\multiarray\textreading\conversions.c
/*
* Include Python.h header file to interface with Python API.
*/
/*
* Include necessary standard C library headers.
*/
/*
* Define NPY_NO_DEPRECATED_API to specify the version of NumPy API to use.
*/
/*
* Include the lowlevel_strided_loops.h header file for low-level strided loops.
*/
/*
* Include NumPy headers for mathematical functions (npy_math.h),
* type conversions (conversions.h), and string to integer conversion (str_to_int.h).
*/
/*
* Include array_coercion.h for handling array coercion operations.
*/
/*
* Coercion to boolean is done via integer right now.
*/
NPY_NO_EXPORT int
npy_to_bool(PyArray_Descr *NPY_UNUSED(descr),
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *NPY_UNUSED(pconfig))
{
int64_t res;
/*
* Convert UCS4 string to int64 using str_to_int64 function,
* and store the result in res.
*/
if (str_to_int64(str, end, INT64_MIN, INT64_MAX, &res) < 0) {
return -1;
}
/*
* Store the boolean value (cast from res) in dataptr.
*/
*dataptr = (char)(res != 0);
return 0;
}
/*
* In order to not pack a whole copy of a floating point parser, we copy the
* result into ascii and call the Python one. Float parsing isn't super quick
* so this is not terrible, but avoiding it would speed up things.
*
* Also note that parsing the first float of a complex will copy the whole
* string to ascii rather than just the first part.
* TODO: A tweak of the break might be a simple mitigation there.
*
* @param str The UCS4 string to parse
* @param end Pointer to the end of the string
* @param skip_trailing_whitespace If false does not skip trailing whitespace
* (used by the complex parser).
* @param result Output stored as double value.
*/
static inline int
double_from_ucs4(
const Py_UCS4 *str, const Py_UCS4 *end,
bool strip_whitespace, double *result, const Py_UCS4 **p_end)
{
/* skip leading whitespace */
if (strip_whitespace) {
while (Py_UNICODE_ISSPACE(*str)) {
str++;
}
}
/*
* If the string is empty or consists only of whitespace, return -1
* indicating it's not a valid floating point number.
*/
if (str == end) {
return -1;
}
/* We convert to ASCII for the Python parser, use stack if small: */
char stack_buf[128];
char *heap_buf = NULL;
char *ascii = stack_buf;
/*
* Determine the length of the UCS4 string to allocate buffer.
* If length exceeds 128, allocate heap memory; otherwise, use stack buffer.
*/
size_t str_len = end - str + 1;
if (str_len > 128) {
heap_buf = PyMem_MALLOC(str_len);
if (heap_buf == NULL) {
PyErr_NoMemory();
return -1;
}
ascii = heap_buf;
}
char *c = ascii;
/*
* Convert UCS4 characters to ASCII characters.
* If encountering non-ASCII characters, ignore them.
*/
for (; str < end; str++, c++) {
if (NPY_UNLIKELY(*str >= 128)) {
/* Character cannot be used, stop conversion and update end pointer. */
end = str;
break;
}
*c = (char)(*str);
}
*c = '\0';
/*
* Use PyOS_string_to_double to parse ASCII string to double.
* Store the parsed value in result, and update end_parsed for tracking
* the position of the last parsed character.
*/
char *end_parsed;
*result = PyOS_string_to_double(ascii, &end_parsed, NULL);
/* Rewind `end` to the first UCS4 character not parsed: */
end = end - (c - end_parsed);
/*
* Free heap buffer if allocated.
*/
PyMem_FREE(heap_buf);
/*
* Check if PyOS_string_to_double encountered an error.
*/
if (*result == -1. && PyErr_Occurred()) {
return -1;
}
/*
* If strip_whitespace is true, skip any remaining whitespace characters in end.
*/
if (strip_whitespace) {
while (Py_UNICODE_ISSPACE(*end)) {
end++;
}
}
*p_end = end;
return 0;
}
// 将传入的字符串解析为浮点数,并存储为单精度浮点数格式到指定位置
npy_to_float(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *NPY_UNUSED(pconfig))
{
double double_val;
const Py_UCS4 *p_end;
// 使用 double_from_ucs4 函数将字符串解析为双精度浮点数
if (double_from_ucs4(str, end, true, &double_val, &p_end) < 0) {
return -1; // 解析失败则返回 -1
}
// 检查解析结束位置是否和字符串末尾相同,如果不同则返回 -1
if (p_end != end) {
return -1;
}
// 将双精度浮点数转换为单精度浮点数,并复制到指定位置
float val = (float)double_val;
memcpy(dataptr, &val, sizeof(float));
// 如果字节顺序不是网络字节顺序,则进行字节交换以适应网络字节顺序
if (!PyArray_ISNBO(descr->byteorder)) {
npy_bswap4_unaligned(dataptr);
}
return 0; // 返回成功标志
}
// 将传入的字符串解析为双精度浮点数,并存储到指定位置
NPY_NO_EXPORT int
npy_to_double(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *NPY_UNUSED(pconfig))
{
double val;
const Py_UCS4 *p_end;
// 使用 double_from_ucs4 函数将字符串解析为双精度浮点数
if (double_from_ucs4(str, end, true, &val, &p_end) < 0) {
return -1; // 解析失败则返回 -1
}
// 检查解析结束位置是否和字符串末尾相同,如果不同则返回 -1
if (p_end != end) {
return -1;
}
// 将双精度浮点数直接复制到指定位置
memcpy(dataptr, &val, sizeof(double));
// 如果字节顺序不是网络字节顺序,则进行字节交换以适应网络字节顺序
if (!PyArray_ISNBO(descr->byteorder)) {
npy_bswap8_unaligned(dataptr);
}
return 0; // 返回成功标志
}
// 解析包含实部和虚部的复数字符串,存储实部和虚部到指定位置
static bool
to_complex_int(
const Py_UCS4 *item, const Py_UCS4 *token_end,
double *p_real, double *p_imag,
Py_UCS4 imaginary_unit, bool allow_parens)
{
const Py_UCS4 *p_end;
bool unmatched_opening_paren = false;
// 跳过可能的起始空白字符,处理可能的开头 '('
while (Py_UNICODE_ISSPACE(*item)) {
++item;
}
if (allow_parens && (*item == '(')) {
unmatched_opening_paren = true;
++item;
// 允许括号内的空白字符,例如 "( 1j)"
while (Py_UNICODE_ISSPACE(*item)) {
++item;
}
}
// 解析实部的双精度浮点数
if (double_from_ucs4(item, token_end, false, p_real, &p_end) < 0) {
return false; // 解析失败则返回 false
}
// 检查是否没有虚部,如果没有,则将虚部设为 0
if (p_end == token_end) {
*p_imag = 0.0;
return !unmatched_opening_paren; // 如果有未匹配的括号则返回 false
}
// 如果虚部部分以虚数单位开始,解析虚部
if (*p_end == imaginary_unit) {
*p_imag = *p_real;
*p_real = 0.0;
++p_end; // 跳过虚数单位字符
}
else if (*p_end == '+' || *p_end == '-') {
// 如果虚部部分以正负号开始,解析剩余的虚部
if (*p_end == '+') {
++p_end; // 支持 '+-' 和 '++'
}
if (double_from_ucs4(p_end, token_end, false, p_imag, &p_end) < 0) {
return false; // 解析失败则返回 false
}
if (*p_end != imaginary_unit) {
return false; // 虚部部分未以虚数单位结束,返回 false
}
++p_end; // 跳过虚数单位字符
}
else {
*p_imag = 0; // 没有虚部
}
// 如果有未匹配的括号,允许括号内的空白字符
if (unmatched_opening_paren) {
while (Py_UNICODE_ISSPACE(*p_end)) {
++p_end;
}
if (*p_end == ')') {
++p_end; // 如果匹配到右括号则跳过
}
else {
return false; // 括号未闭合,返回 false
}
}
// 跳过可能的结尾空白字符
while (Py_UNICODE_ISSPACE(*p_end)) {
++p_end;
}
// 检查是否已经解析到 token_end 结尾
return p_end == token_end;
}
/*
* 将 numpy 数据描述符 `descr` 中的字符串或 Unicode 数据转换为复数浮点数。
*
* Parameters:
* - descr: NumPy 数据描述符指针,描述要转换的数据类型和字节顺序
* - str: 要转换的输入字符串或 Unicode 数据的起始位置
* - end: 输入数据的结束位置
* - dataptr: 指向输出数据缓冲区的指针
* - pconfig: 解析器配置结构体指针,包含有关复数单位的信息
*
* Returns:
* - 成功返回 0,失败返回 -1
*/
NPY_NO_EXPORT int
npy_to_cfloat(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *pconfig)
{
double real;
double imag;
// 调用辅助函数 to_complex_int 尝试将输入解析为复数整数
bool success = to_complex_int(
str, end, &real, &imag,
pconfig->imaginary_unit, true);
// 如果解析失败,返回 -1
if (!success) {
return -1;
}
// 创建 numpy 的复数浮点数结构 npy_complex64
npy_complex64 val;
npy_csetrealf(&val, (float) real); // 设置实部
npy_csetimagf(&val, (float) imag); // 设置虚部
// 将复数数据复制到输出缓冲区
memcpy(dataptr, &val, sizeof(npy_complex64));
// 如果数据字节顺序不是本地字节顺序,则进行字节交换
if (!PyArray_ISNBO(descr->byteorder)) {
npy_bswap4_unaligned(dataptr); // 交换前四个字节
npy_bswap4_unaligned(dataptr + 4); // 交换后四个字节
}
// 返回转换结果
return 0;
}
/*
* 将 numpy 数据描述符 `descr` 中的字符串或 Unicode 数据转换为双精度复数浮点数。
*
* Parameters:
* - descr: NumPy 数据描述符指针,描述要转换的数据类型和字节顺序
* - str: 要转换的输入字符串或 Unicode 数据的起始位置
* - end: 输入数据的结束位置
* - dataptr: 指向输出数据缓冲区的指针
* - pconfig: 解析器配置结构体指针,包含有关复数单位的信息
*
* Returns:
* - 成功返回 0,失败返回 -1
*/
NPY_NO_EXPORT int
npy_to_cdouble(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *pconfig)
{
double real;
double imag;
// 调用辅助函数 to_complex_int 尝试将输入解析为复数整数
bool success = to_complex_int(
str, end, &real, &imag, pconfig->imaginary_unit, true);
// 如果解析失败,返回 -1
if (!success) {
return -1;
}
// 创建 numpy 的双精度复数浮点数结构 npy_complex128
npy_complex128 val;
npy_csetreal(&val, real); // 设置实部
npy_csetimag(&val, imag); // 设置虚部
// 将复数数据复制到输出缓冲区
memcpy(dataptr, &val, sizeof(npy_complex128));
// 如果数据字节顺序不是本地字节顺序,则进行字节交换
if (!PyArray_ISNBO(descr->byteorder)) {
npy_bswap8_unaligned(dataptr); // 交换前八个字节
npy_bswap8_unaligned(dataptr + 8); // 交换后八个字节
}
// 返回转换结果
return 0;
}
/*
* 将 numpy 数据描述符 `descr` 中的字符串或 Unicode 数据转换为字节字符串。
*
* Parameters:
* - descr: NumPy 数据描述符指针,描述要转换的数据类型和字节顺序
* - str: 要转换的输入字符串或 Unicode 数据的起始位置
* - end: 输入数据的结束位置
* - dataptr: 指向输出数据缓冲区的指针
* - unused: 未使用的解析器配置结构体指针
*
* Returns:
* - 成功返回 0,失败返回 -1
*/
NPY_NO_EXPORT int
npy_to_string(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *NPY_UNUSED(unused))
{
const Py_UCS4* c = str;
size_t length = descr->elsize;
// 遍历输入字符串或 Unicode 数据,逐字符转换为 Latin1 编码的字节
for (size_t i = 0; i < length; i++) {
if (c < end) {
// 如果字符超出 Latin1 范围,返回解析错误
if (NPY_UNLIKELY(*c > 255)) {
/* TODO: Was UnicodeDecodeError, is unspecific error good? */
return -1;
}
dataptr[i] = (Py_UCS1)(*c); // 将 Unicode 字符转换为 Latin1 字节
c++;
}
else {
dataptr[i] = '\0'; // 如果输入结束,填充剩余空间为 '\0'
}
}
// 返回转换结果
return 0;
}
/*
* 将 numpy 数据描述符 `descr` 中的字符串或 Unicode 数据转换为 Unicode 字符串。
*
* Parameters:
* - descr: NumPy 数据描述符指针,描述要转换的数据类型和字节顺序
* - str: 要转换的输入字符串或 Unicode 数据的起始位置
* - end: 输入数据的结束位置
* - dataptr: 指向输出数据缓冲区的指针
* - unused: 未使用的解析器配置结构体指针
*
* Returns:
* - 成功返回 0,失败返回 -1
*/
NPY_NO_EXPORT int
npy_to_unicode(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *NPY_UNUSED(unused))
{
int length = descr->elsize / 4; // 计算要复制的 Unicode 字符数
// 如果输入长度不超过给定结束位置到起始位置的距离,直接复制
if (length <= end - str) {
memcpy(dataptr, str, length * 4);
}
else {
size_t given_len = end - str;
memcpy(dataptr, str, given_len * 4); // 复制已给定长度的数据
memset(dataptr + given_len * 4, '\0', (length - given_len) * 4); // 填充剩余空间为 '\0'
}
// 如果数据字节顺序不是本地字节顺序,则进行字节交换
if (!PyArray_ISNBO(descr->byteorder)) {
for (int i = 0; i < length; i++) {
npy_bswap4_unaligned(dataptr); // 逐个字符进行字节交换
dataptr += 4;
}
}
// 返回转换结果
return 0;
}
}
// 如果需要进行字节转换操作
if (byte_converters) {
// 将字符串对象转换为 latin1 编码的字节串对象
Py_SETREF(s, PyUnicode_AsEncodedString(s, "latin1", NULL));
// 检查转换后的结果是否为 NULL
if (s == NULL) {
// 如果转换失败,返回 NULL
return NULL;
}
}
// 如果 func 为 NULL,则直接返回字符串对象 s
if (func == NULL) {
return s;
}
// 否则,调用指定的 Python 函数 func,并传入字符串对象 s 作为参数
PyObject *result = PyObject_CallFunctionObjArgs(func, s, NULL);
// 释放字符串对象 s 的引用
Py_DECREF(s);
// 返回函数调用的结果对象
return result;
NPY_NO_EXPORT int
npy_to_generic_with_converter(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *config, PyObject *func)
{
bool use_byte_converter;
if (func == NULL) {
use_byte_converter = config->c_byte_converters;
}
else {
use_byte_converter = config->python_byte_converters;
}
/* Converts to unicode and calls custom converter (if set) */
PyObject *converted = call_converter_function(
func, str, (size_t)(end - str), use_byte_converter);
if (converted == NULL) {
return -1;
}
int res = PyArray_Pack(descr, dataptr, converted);
Py_DECREF(converted);
return res;
}
NPY_NO_EXPORT int
npy_to_generic(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *config)
{
return npy_to_generic_with_converter(descr, str, end, dataptr, config, NULL);
}
.\numpy\numpy\_core\src\multiarray\textreading\conversions.h
// 包含标准布尔类型的头文件
// 定义宏,指定不使用废弃的 API 版本
// 定义宏,标记当前为多维数组模块
// 包含 NumPy 的数组对象头文件
// 包含文本解析器配置文件的头文件
// 声明不导出的函数,用于将字符串转换为布尔类型
NPY_NO_EXPORT int
npy_to_bool(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *pconfig);
// 声明不导出的函数,用于将字符串转换为单精度浮点数类型
NPY_NO_EXPORT int
npy_to_float(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *pconfig);
// 声明不导出的函数,用于将字符串转换为双精度浮点数类型
NPY_NO_EXPORT int
npy_to_double(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *pconfig);
// 声明不导出的函数,用于将字符串转换为单精度复数类型
NPY_NO_EXPORT int
npy_to_cfloat(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *pconfig);
// 声明不导出的函数,用于将字符串转换为双精度复数类型
NPY_NO_EXPORT int
npy_to_cdouble(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *pconfig);
// 声明不导出的函数,用于将字符串转换为字符串类型
NPY_NO_EXPORT int
npy_to_string(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *unused);
// 声明不导出的函数,用于将字符串转换为 Unicode 类型
NPY_NO_EXPORT int
npy_to_unicode(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *unused);
// 声明不导出的函数,用于通过自定义转换器将字符串转换为通用类型
NPY_NO_EXPORT int
npy_to_generic_with_converter(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *unused, PyObject *func);
// 声明不导出的函数,用于将字符串通过默认转换器转换为通用类型
NPY_NO_EXPORT int
npy_to_generic(PyArray_Descr *descr,
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr,
parser_config *pconfig);
注释:
.\numpy\numpy\_core\src\multiarray\textreading\field_types.c
/*
* 包含必要的头文件来引入所需的类型和函数声明
*/
/*
* 定义宏,指定使用的 NumPy API 版本,禁用已弃用的 API
*/
/*
* 引入特定领域增长的自定义头文件
*/
/*
* 清除字段类型数组及其描述符的函数
*/
NPY_NO_EXPORT void
field_types_xclear(int num_field_types, field_type *ft) {
assert(num_field_types >= 0);
if (ft == NULL) {
return;
}
for (int i = 0; i < num_field_types; i++) {
Py_XDECREF(ft[i].descr); // 释放 Python 对象的引用
ft[i].descr = NULL; // 将字段描述符指针设为 NULL
}
PyMem_Free(ft); // 释放内存
}
/*
* 获取从 UCS4 编码到指定 NumPy DType 的转换函数
*/
static set_from_ucs4_function *
get_from_ucs4_function(PyArray_Descr *descr)
{
if (descr->type_num == NPY_BOOL) {
return &npy_to_bool; // 返回布尔型转换函数指针
}
else if (PyDataType_ISSIGNED(descr)) {
switch (descr->elsize) {
case 1:
return &npy_to_int8; // 返回 int8 转换函数指针
case 2:
return &npy_to_int16; // 返回 int16 转换函数指针
case 4:
return &npy_to_int32; // 返回 int32 转换函数指针
case 8:
return &npy_to_int64; // 返回 int64 转换函数指针
default:
assert(0); // 断言,如果不是预期的大小则终止程序
}
}
else if (PyDataType_ISUNSIGNED(descr)) {
switch (descr->elsize) {
case 1:
return &npy_to_uint8; // 返回 uint8 转换函数指针
case 2:
return &npy_to_uint16; // 返回 uint16 转换函数指针
case 4:
return &npy_to_uint32; // 返回 uint32 转换函数指针
case 8:
return &npy_to_uint64; // 返回 uint64 转换函数指针
default:
assert(0); // 断言,如果不是预期的大小则终止程序
}
}
else if (descr->type_num == NPY_FLOAT) {
return &npy_to_float; // 返回 float 转换函数指针
}
else if (descr->type_num == NPY_DOUBLE) {
return &npy_to_double; // 返回 double 转换函数指针
}
else if (descr->type_num == NPY_CFLOAT) {
return &npy_to_cfloat; // 返回复数浮点数转换函数指针
}
else if (descr->type_num == NPY_CDOUBLE) {
return &npy_to_cdouble; // 返回复数双精度转换函数指针
}
else if (descr->type_num == NPY_STRING) {
return &npy_to_string; // 返回字符串转换函数指针
}
else if (descr->type_num == NPY_UNICODE) {
return &npy_to_unicode; // 返回 Unicode 转换函数指针
}
return &npy_to_generic; // 默认返回通用转换函数指针
}
/*
* 递归增长字段类型数组的函数
*/
static npy_intp
field_type_grow_recursive(PyArray_Descr *descr,
npy_intp num_field_types, field_type **ft, npy_intp *ft_size,
npy_intp field_offset)
{
if (PyDataType_HASSUBARRAY(descr)) {
PyArray_Dims shape = {NULL, -1};
if (!(PyArray_IntpConverter(PyDataType_SUBARRAY(descr)->shape, &shape))) {
PyErr_SetString(PyExc_ValueError, "invalid subarray shape");
field_types_xclear(num_field_types, *ft);
return -1;
}
npy_intp size = PyArray_MultiplyList(shape.ptr, shape.len);
npy_free_cache_dim_obj(shape);
for (npy_intp i = 0; i < size; i++) {
num_field_types = field_type_grow_recursive(PyDataType_SUBARRAY(descr)->base,
num_field_types, ft, ft_size, field_offset);
field_offset += PyDataType_SUBARRAY(descr)->base->elsize;
if (num_field_types < 0) {
return -1;
}
}
return num_field_types;
}
else if (PyDataType_HASFIELDS(descr)) {
npy_int num_descr_fields = PyTuple_Size(PyDataType_NAMES(descr));
if (num_descr_fields < 0) {
field_types_xclear(num_field_types, *ft);
return -1;
}
for (npy_intp i = 0; i < num_descr_fields; i++) {
PyObject *key = PyTuple_GET_ITEM(PyDataType_NAMES(descr), i);
PyObject *tup = PyObject_GetItem(PyDataType_FIELDS(descr), key);
if (tup == NULL) {
field_types_xclear(num_field_types, *ft);
return -1;
}
PyArray_Descr *field_descr;
PyObject *title;
int offset;
if (!PyArg_ParseTuple(tup, "Oi|O", &field_descr, &offset, &title)) {
Py_DECREF(tup);
field_types_xclear(num_field_types, *ft);
return -1;
}
Py_DECREF(tup);
num_field_types = field_type_grow_recursive(
field_descr, num_field_types, ft, ft_size,
field_offset + offset);
if (num_field_types < 0) {
return -1;
}
}
return num_field_types;
}
if (*ft_size <= num_field_types) {
npy_intp alloc_size = grow_size_and_multiply(
ft_size, 4, sizeof(field_type));
if (alloc_size < 0) {
field_types_xclear(num_field_types, *ft);
return -1;
}
field_type *new_ft = PyMem_Realloc(*ft, alloc_size);
if (new_ft == NULL) {
field_types_xclear(num_field_types, *ft);
return -1;
}
*ft = new_ft;
}
Py_INCREF(descr);
(*ft)[num_field_types].descr = descr;
(*ft)[num_field_types].set_from_ucs4 = get_from_ucs4_function(descr);
(*ft)[num_field_types].structured_offset = field_offset;
return num_field_types + 1;
}
/*
* Prepare the "field_types" for the given dtypes/descriptors. Currently,
* we copy the itemsize, but the main thing is that we check for custom
* converters.
*/
NPY_NO_EXPORT npy_intp
field_types_create(PyArray_Descr *descr, field_type **ft)
{
// 如果输入的数据类型是子数组(subarray),则抛出类型错误异常
if (PyDataType_SUBARRAY(descr) != NULL) {
PyErr_SetString(PyExc_TypeError,
"file reader does not support subarray dtypes. You can"
"put the dtype into a structured one using "
"`np.dtype(('name', dtype))` to avoid this limitation.");
return -1;
}
// 初始化初始 field_type 数组的大小为 4
npy_intp ft_size = 4;
// 分配内存以存储 field_type 数组
*ft = PyMem_Malloc(ft_size * sizeof(field_type));
// 检查内存分配是否成功
if (*ft == NULL) {
return -1;
}
// 递归地填充 field_type 数组,并返回填充的元素个数
return field_type_grow_recursive(descr, 0, ft, &ft_size, 0);
}
.\numpy\numpy\_core\src\multiarray\textreading\field_types.h
/**
* Function defining the conversion for each value.
*
* This function must support unaligned memory access. As of now, there is
* no special error handling (in whatever form): We assume that it is always
* reasonable to raise a `ValueError` noting the string that failed to be
* converted.
*
* NOTE: An earlier version of the code had unused default values (pandas
* does this) when columns are missing. We could define this either
* by passing `NULL` in, or by adding a default explicitly somewhere.
* (I think users should probably have to define the default, at which
* point it doesn't matter here.)
*
* NOTE: We are currently passing the parser config, this could be made public
* or could be set up to be dtype specific/private. Always passing
* pconfig fully seems easier right now even if it may change.
* (A future use-case may for example be user-specified strings that are
* considered boolean True or False).
*
* TODO: Aside from nailing down the above notes, it may be nice to expose
* these function publicly. This could allow user DTypes to provide
* a converter or custom converters written in C rather than Python.
*
* @param descr The NumPy descriptor of the field (may be byte-swapped, etc.)
* @param str Pointer to the beginning of the UCS4 string to be parsed.
* @param end Pointer to the end of the UCS4 string. This value is currently
* guaranteed to be `\0`, ensuring that parsers can rely on
* nul-termination.
* @param dataptr The pointer where to store the parsed value
* @param pconfig Additional configuration for the parser.
* @returns 0 on success and -1 on failure. If the return value is -1 an
* error may or may not be set. If an error is set, it is chained
* behind the generic ValueError.
*/
typedef int (set_from_ucs4_function)(
PyArray_Descr *descr, const Py_UCS4 *str, const Py_UCS4 *end,
char *dataptr, parser_config *pconfig);
/**
* Structure defining a field type for text parsing.
*/
typedef struct _field_type {
set_from_ucs4_function *set_from_ucs4; // Function pointer for converting UCS4 strings
/* The original NumPy descriptor */
PyArray_Descr *descr; // NumPy descriptor of the field
/* Offset to this entry within row. */
npy_intp structured_offset; // Offset of this field within a structured row
} field_type;
/**
* Clears memory allocated for field types.
*
* @param num_field_types Number of field types to clear
* @param ft Array of field types
*/
NPY_NO_EXPORT void
field_types_xclear(int num_field_types, field_type *ft);
/**
* Creates field types based on a NumPy descriptor.
*
* @param descr NumPy descriptor specifying the fields
* @param ft Output parameter to hold the created field types
* @return Number of field types created
*/
NPY_NO_EXPORT npy_intp
field_types_create(PyArray_Descr *descr, field_type **ft);
#endif /* NUMPY_CORE_SRC_MULTIARRAY_TEXTREADING_FIELD_TYPES_H_ */
.\numpy\numpy\_core\src\multiarray\textreading\growth.c
/*
* 宏定义,用于设置 NumPy 不使用过时的 API 版本,使用当前的 API 版本
*/
/*
* 宏定义,指定当前文件是多维数组模块的一部分
*/
/*
* 包含 NumPy 头文件,用于获取 ndarray 类型相关的定义
*/
/*
* 包含模板共享的头文件 templ_common.h
*/
/*
* 辅助函数:根据输入的大小信息和最小增长值来计算新的大小。
* 当前方案是通过一定比例的增长(25%)来进行动态扩展,并且限制在 2**20 个元素以内,
* 因为这使得我们处于大页大小的范围内(通常已经足够)。
*
* 进一步将新的大小乘以每个元素的大小 itemsize,并确保所有的结果适合于 npy_intp 类型。
* 如果发生溢出或者结果无法适应,则返回 -1。
* 调用者需要确保输入的 size 是 ssize_t 类型且不为负数。
*/
NPY_NO_EXPORT npy_intp
grow_size_and_multiply(npy_intp *size, npy_intp min_grow, npy_intp itemsize) {
/* min_grow 必须是二的幂:*/
assert((min_grow & (min_grow - 1)) == 0);
// 将 size 转换为无符号整数类型
npy_uintp new_size = (npy_uintp)*size;
// 计算增长量,初始增长为当前大小的四分之一
npy_intp growth = *size >> 2;
// 如果增长量小于等于 min_grow,则使用 min_grow
if (growth <= min_grow) {
new_size += min_grow;
}
else {
// 如果增长量大于 1 << 20,则限制在 1 << 20 范围内
if (growth > 1 << 20) {
growth = 1 << 20;
}
// 计算新的大小,保证是 min_grow 的倍数,并且不超过 NPY_MAX_INTP
new_size += growth + min_grow - 1;
new_size &= ~min_grow;
if (new_size > NPY_MAX_INTP) {
// 如果超过了 npy_intp 的最大值,则返回 -1 表示溢出
return -1;
}
}
// 将计算后的新大小赋值给 size
*size = (npy_intp)new_size;
// 计算最终的分配大小,即 new_size 乘以 itemsize
npy_intp alloc_size;
if (npy_mul_sizes_with_overflow(&alloc_size, (npy_intp)new_size, itemsize)) {
// 如果乘法溢出,则返回 -1
return -1;
}
// 返回最终的分配大小
return alloc_size;
}
.\numpy\numpy\_core\src\multiarray\textreading\growth.h
extern "C" {
// NPY_NO_EXPORT 宏定义用于标记函数或变量不会被动态链接到共享库中
// grow_size_and_multiply 函数用于增长数组大小并计算乘积,返回结果为增长后的大小
NPY_NO_EXPORT npy_intp
grow_size_and_multiply(npy_intp *size, npy_intp min_grow, npy_intp itemsize);
}
.\numpy\numpy\_core\src\multiarray\textreading\parser_config.h
extern "C" {
typedef struct {
/*
* Field delimiter character.
* Typically ',', ' ', '\t', ignored if `delimiter_is_whitespace` is true.
*/
Py_UCS4 delimiter;
/*
* Character used to quote fields.
* Typically '"' or "'". To disable quoting we set this to UINT_MAX
* (which is not a valid unicode character and thus cannot occur in the
* file; the same is used for all other characters if necessary).
*/
Py_UCS4 quote;
/*
* Character(s) that indicates the start of a comment.
* Typically '#', '%' or ';'.
* When encountered in a line and not inside quotes, all characters
* from the comment character(s) to the end of the line are ignored.
*/
Py_UCS4 comment;
/*
* Ignore whitespace at the beginning of a field (outside/before quotes).
* Is (and must be) set if `delimiter_is_whitespace`.
*/
bool ignore_leading_whitespace;
/*
* If true, the delimiter is ignored and any unicode whitespace is used
* for splitting (same as `string.split()` in Python). In that case
* `ignore_leading_whitespace` should also be set.
*/
bool delimiter_is_whitespace;
/*
* The imaginary unit character. Default is `j`.
*/
Py_UCS4 imaginary_unit;
/*
* Data should be encoded as `latin1` when using python converter
* (implementing `loadtxt` default Python 2 compatibility mode).
* The c byte converter is used when the user requested `dtype="S"`.
* In this case we go via `dtype=object`, however, loadtxt allows latin1
* while normal object to string casts only accept ASCII, so it ensures
* that that the object array already contains bytes and not strings.
*/
bool python_byte_converters;
bool c_byte_converters;
/*
* Flag to store whether a warning was already given for an integer being
* parsed by first converting to a float.
*/
bool gave_int_via_float_warning;
} parser_config;
}
.\numpy\numpy\_core\src\multiarray\textreading\readtext.c
// 引入必要的头文件和库
// 引入文本解析所需的自定义头文件
// `_readtext_from_stream` 函数定义,用于从流中读取文本数据
static PyObject *
_readtext_from_stream(stream *s,
parser_config *pc, Py_ssize_t num_usecols, Py_ssize_t usecols[],
Py_ssize_t skiplines, Py_ssize_t max_rows,
PyObject *converters, PyObject *dtype)
{
PyArrayObject *arr = NULL;
PyArray_Descr *out_dtype = NULL;
field_type *ft = NULL;
/*
* 如果 `dtype` 是结构化的,那么将其转换为 `PyArray_Descr` 类型,
* 并增加其引用计数。
*/
out_dtype = (PyArray_Descr *)dtype;
Py_INCREF(out_dtype);
// 创建字段类型描述,并返回字段数量
Py_ssize_t num_fields = field_types_create(out_dtype, &ft);
if (num_fields < 0) {
goto finish; // 如果创建失败,跳转到结束标签
}
// 检查数据是否同构,即是否只有一个字段且类型相同
bool homogeneous = num_fields == 1 && ft[0].descr == out_dtype;
// 如果数据不同构且 `usecols` 不为空,且其数量不等于字段数量,则引发错误
if (!homogeneous && usecols != NULL && num_usecols != num_fields) {
PyErr_Format(PyExc_TypeError,
"If a structured dtype is used, the number of columns in "
"`usecols` must match the effective number of fields. "
"But %zd usecols were given and the number of fields is %zd.",
num_usecols, num_fields);
goto finish; // 跳转到结束标签
}
// 调用 `read_rows` 函数从流中读取数据行,并返回一个 `PyArrayObject` 对象
arr = read_rows(
s, max_rows, num_fields, ft, pc,
num_usecols, usecols, skiplines, converters,
NULL, out_dtype, homogeneous);
if (arr == NULL) {
goto finish; // 如果读取失败,跳转到结束标签
}
finish:
// 释放 `out_dtype` 的引用
Py_XDECREF(out_dtype);
// 清理字段类型描述数组
field_types_xclear(num_fields, ft);
// 返回 `arr` 对象
return (PyObject *)arr;
}
// `parse_control_character` 函数定义,用于解析控制字符
static int
parse_control_character(PyObject *obj, Py_UCS4 *character)
{
// 如果传入的对象是 `Py_None`,则设置字符为超出 Unicode 范围的值
if (obj == Py_None) {
*character = (Py_UCS4)-1; /* character beyond unicode range */
return 1; // 返回解析成功标志
}
if (!PyUnicode_Check(obj) || PyUnicode_GetLength(obj) != 1) {
PyErr_Format(PyExc_TypeError,
"Text reading control character must be a single unicode "
"character or None; but got: %.100R", obj);
return 0;
}
*character = PyUnicode_READ_CHAR(obj, 0);
return 1;
/*
* A (somewhat verbose) check that none of the control characters match or are
* newline. Most of these combinations are completely fine, just weird or
* surprising.
* (I.e. there is an implicit priority for control characters, so if a comment
* matches a delimiter, it would just be a comment.)
* In theory some `delimiter=None` paths could have a "meaning", but let us
* assume that users are better off setting one of the control chars to `None`
* for clarity.
*
* This also checks that the control characters cannot be newlines.
*/
static int
error_if_matching_control_characters(
Py_UCS4 delimiter, Py_UCS4 quote, Py_UCS4 comment)
{
char *control_char1; // 声明一个指向控制字符名称的指针
char *control_char2 = NULL; // 初始化第二个控制字符名称的指针为 NULL
if (comment != (Py_UCS4)-1) { // 如果注释字符不是特殊值 -1
control_char1 = "comment"; // 设置第一个控制字符名称为 "comment"
if (comment == '\r' || comment == '\n') { // 如果注释字符是回车或换行
goto error; // 跳转到错误处理部分
}
else if (comment == quote) { // 如果注释字符等于引号字符
control_char2 = "quotechar"; // 设置第二个控制字符名称为 "quotechar"
goto error; // 跳转到错误处理部分
}
else if (comment == delimiter) { // 如果注释字符等于分隔符字符
control_char2 = "delimiter"; // 设置第二个控制字符名称为 "delimiter"
goto error; // 跳转到错误处理部分
}
}
if (quote != (Py_UCS4)-1) { // 如果引号字符不是特殊值 -1
control_char1 = "quotechar"; // 设置第一个控制字符名称为 "quotechar"
if (quote == '\r' || quote == '\n') { // 如果引号字符是回车或换行
goto error; // 跳转到错误处理部分
}
else if (quote == delimiter) { // 如果引号字符等于分隔符字符
control_char2 = "delimiter"; // 设置第二个控制字符名称为 "delimiter"
goto error; // 跳转到错误处理部分
}
}
if (delimiter != (Py_UCS4)-1) { // 如果分隔符字符不是特殊值 -1
control_char1 = "delimiter"; // 设置第一个控制字符名称为 "delimiter"
if (delimiter == '\r' || delimiter == '\n') { // 如果分隔符字符是回车或换行
goto error; // 跳转到错误处理部分
}
}
/* The above doesn't work with delimiter=None, which means "whitespace" */
if (delimiter == (Py_UCS4)-1) { // 如果分隔符字符是特殊值 -1,表示“空白符”
control_char1 = "delimiter"; // 设置第一个控制字符名称为 "delimiter"
if (Py_UNICODE_ISSPACE(comment)) { // 如果注释字符是空白符
control_char2 = "comment"; // 设置第二个控制字符名称为 "comment"
goto error; // 跳转到错误处理部分
}
else if (Py_UNICODE_ISSPACE(quote)) { // 如果引号字符是空白符
control_char2 = "quotechar"; // 设置第二个控制字符名称为 "quotechar"
goto error; // 跳转到错误处理部分
}
}
return 0; // 没有发现控制字符匹配问题,返回 0 表示无错误
error:
if (control_char2 != NULL) {
PyErr_Format(PyExc_TypeError,
"The values for control characters '%s' and '%s' are "
"incompatible",
control_char1, control_char2); // 报告两个控制字符值不兼容的错误
}
else {
PyErr_Format(PyExc_TypeError,
"control character '%s' cannot be a newline (`\\r` or `\\n`).",
control_char1); // 报告控制字符不能是换行符的错误
}
return -1; // 返回 -1 表示发生了错误
}
/*
* This function loads data from a file-like object, processing various
* parameters and options related to data loading.
*/
NPY_NO_EXPORT PyObject *
_load_from_filelike(PyObject *NPY_UNUSED(mod),
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
PyObject *file; // 声明一个文件对象的指针
Py_ssize_t skiplines = 0; // 初始化跳过行数为 0
Py_ssize_t max_rows = -1; // 初始化最大行数为 -1
PyObject *usecols_obj = Py_None; // 初始化列使用对象为 None
PyObject *converters = Py_None; // 初始化转换器对象为 None
PyObject *dtype = Py_None; // 初始化数据类型为 None
PyObject *encoding_obj = Py_None; // 初始化编码对象为 None
const char *encoding = NULL; // 初始化编码字符串为 NULL
}
// 定义并初始化一个解析器配置结构体 pc,包括分隔符、引号、注释符、是否忽略前导空白、
// 分隔符是否为空白、虚数单位、Python 字节转换器、C 字节转换器、是否提供整数通过浮点数警告
parser_config pc = {
.delimiter = ',',
.quote = '"',
.comment = '#',
.ignore_leading_whitespace = false,
.delimiter_is_whitespace = false,
.imaginary_unit = 'j',
.python_byte_converters = false,
.c_byte_converters = false,
.gave_int_via_float_warning = false,
};
// 定义并初始化一个布尔值变量 filelike,表示是否类似文件
bool filelike = true;
// 初始化 PyObject 指针变量 arr,赋值为 NULL
PyObject *arr = NULL;
// 准备 NumPy 参数解析器宏的调用
NPY_PREPARE_ARGPARSER;
// 解析传入参数,如果解析失败则返回空指针
if (npy_parse_arguments("_load_from_filelike", args, len_args, kwnames,
"file", NULL, &file,
"|delimiter", &parse_control_character, &pc.delimiter,
"|comment", &parse_control_character, &pc.comment,
"|quote", &parse_control_character, &pc.quote,
"|imaginary_unit", &parse_control_character, &pc.imaginary_unit,
"|usecols", NULL, &usecols_obj,
"|skiplines", &PyArray_IntpFromPyIntConverter, &skiplines,
"|max_rows", &PyArray_IntpFromPyIntConverter, &max_rows,
"|converters", NULL, &converters,
"|dtype", NULL, &dtype,
"|encoding", NULL, &encoding_obj,
"|filelike", &PyArray_BoolConverter, &filelike,
"|byte_converters", &PyArray_BoolConverter, &pc.python_byte_converters,
"|c_byte_converters", PyArray_BoolConverter, &pc.c_byte_converters,
NULL, NULL, NULL) < 0) {
return NULL;
}
// 拒绝匹配的控制字符,因为它们通常没有意义
if (error_if_matching_control_characters(
pc.delimiter, pc.quote, pc.comment) < 0) {
return NULL;
}
// 如果分隔符为特定值 (Py_UCS4)-1,则将 delimiter_is_whitespace 设置为 true,
// 并忽略前导空白以匹配 `string.split(None)`
if (pc.delimiter == (Py_UCS4)-1) {
pc.delimiter_is_whitespace = true;
pc.ignore_leading_whitespace = true;
}
// 如果 dtype 不是有效的 NumPy 数据类型描述符,则设置类型错误并返回空指针
if (!PyArray_DescrCheck(dtype)) {
PyErr_SetString(PyExc_TypeError,
"internal error: dtype must be provided and be a NumPy dtype");
return NULL;
}
// 如果 encoding_obj 不是 None,则检查它是否为 Unicode 字符串,
// 如果不是则设置类型错误并返回空指针;否则将其转换为 UTF-8 编码
if (encoding_obj != Py_None) {
if (!PyUnicode_Check(encoding_obj)) {
PyErr_SetString(PyExc_TypeError,
"encoding must be a unicode string.");
return NULL;
}
encoding = PyUnicode_AsUTF8(encoding_obj);
if (encoding == NULL) {
return NULL;
}
}
/*
* 解析 usecols,因为 NumPy 中没有明确的辅助函数,所以在这里手动处理。
*/
// 初始化 num_usecols 为 -1,usecols 为 NULL
Py_ssize_t num_usecols = -1;
Py_ssize_t *usecols = NULL;
if (usecols_obj != Py_None) {
// 获取 usecols_obj 序列的长度
num_usecols = PySequence_Length(usecols_obj);
if (num_usecols < 0) {
return NULL;
}
/* Calloc just to not worry about overflow */
// 分配足够大小的内存,以存储 usecols 序列
usecols = PyMem_Calloc(num_usecols, sizeof(Py_ssize_t));
if (usecols == NULL) {
PyErr_NoMemory();
return NULL;
}
// 遍历 usecols_obj 序列,将每个元素转换为 Py_ssize_t 存入 usecols
for (Py_ssize_t i = 0; i < num_usecols; i++) {
PyObject *tmp = PySequence_GetItem(usecols_obj, i);
if (tmp == NULL) {
PyMem_FREE(usecols);
return NULL;
}
// 将 tmp 转换为 Py_ssize_t 类型存入 usecols[i]
usecols[i] = PyNumber_AsSsize_t(tmp, PyExc_OverflowError);
if (error_converting(usecols[i])) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
// 若转换出错,生成相应的类型错误异常
PyErr_Format(PyExc_TypeError,
"usecols must be an int or a sequence of ints but "
"it contains at least one element of type '%s'",
Py_TYPE(tmp)->tp_name);
}
Py_DECREF(tmp);
PyMem_FREE(usecols);
return NULL;
}
Py_DECREF(tmp);
}
}
// 根据 filelike 参数选择相应的流处理函数
stream *s;
if (filelike) {
s = stream_python_file(file, encoding);
}
else {
s = stream_python_iterable(file, encoding);
}
// 若流处理函数返回 NULL,则释放 usecols 内存并返回 NULL
if (s == NULL) {
PyMem_FREE(usecols);
return NULL;
}
// 调用 _readtext_from_stream 函数处理流数据,返回处理结果
arr = _readtext_from_stream(
s, &pc, num_usecols, usecols, skiplines, max_rows, converters, dtype);
// 关闭流处理函数
stream_close(s);
// 释放 usecols 内存
PyMem_FREE(usecols);
// 返回处理结果
return arr;
}
注释:
# 结束一个代码块,这里对应着某个函数、循环、条件语句或类定义的结束
.\numpy\numpy\_core\src\multiarray\textreading\readtext.h
NPY_NO_EXPORT PyObject *
_load_from_filelike(PyObject *mod,
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames);
// 声明一个不导出的函数_load_from_filelike,接受模块对象及其它参数
// 定义了一个头文件宏,用于防止多次包含该头文件
.\numpy\numpy\_core\src\multiarray\textreading\rows.c
// 包含 Python 头文件
// 包含 numpy 相关头文件
// 包含标准库头文件
// 包含自定义头文件
/*
* Minimum size to grow the allocation by (or 25%). The 8KiB means the actual
* growths is within `8 KiB <= size < 16 KiB` (depending on the row size).
*/
// 定义最小块大小为 8KB,表示在 `8 KiB <= size < 16 KiB` 之间变动(取决于行大小)
/*
* Create the array of converter functions from the Python converters.
*/
static PyObject **
create_conv_funcs(
PyObject *converters, Py_ssize_t num_fields, const Py_ssize_t *usecols)
{
assert(converters != Py_None);
// 断言 converters 不为 Py_None
PyObject **conv_funcs = PyMem_Calloc(num_fields, sizeof(PyObject *));
// 使用 PyMem_Calloc 为函数创建 PyObject 指针数组
if (conv_funcs == NULL) {
PyErr_NoMemory();
return NULL;
}
if (PyCallable_Check(converters)) {
/* a single converter used for all columns individually */
// 单个转换器用于每一列
for (Py_ssize_t i = 0; i < num_fields; i++) {
Py_INCREF(converters);
conv_funcs[i] = converters;
}
return conv_funcs;
}
else if (!PyDict_Check(converters)) {
PyErr_SetString(PyExc_TypeError,
"converters must be a dictionary mapping columns to converter "
"functions or a single callable.");
goto error;
// 报错:converters 必须是一个将列映射到转换器函数的字典或单个可调用对象
}
PyObject *key, *value;
Py_ssize_t pos = 0;
// 定义变量 key, value, pos
while (PyDict_Next(converters, &pos, &key, &value)) {
// 从 converters 字典中依次获取每对键值对
Py_ssize_t column = PyNumber_AsSsize_t(key, PyExc_IndexError);
// 将 key 转换为 Py_ssize_t 类型,表示列索引
if (column == -1 && PyErr_Occurred()) {
// 如果转换失败或者 column 为 -1 且有错误发生,则报错并跳转到 error 标签
PyErr_Format(PyExc_TypeError,
"keys of the converters dictionary must be integers; "
"got %.100R", key);
goto error;
}
if (usecols != NULL) {
/*
* 这段代码用于查找对应的 usecols。它与传统的 usecols 代码相同,但有两个弱点:
* 1. 对于重复的 usecols 只设置第一个的转换器。
* 2. 如果 usecols 使用负索引而 converters 没有使用,会出错。
* (这是一个特性,因为它允许我们在这里正确规范化转换器到结果列。)
*/
Py_ssize_t i = 0;
for (; i < num_fields; i++) {
// 遍历 num_fields(字段数量),查找匹配的列索引
if (column == usecols[i]) {
column = i;
break;
}
}
if (i == num_fields) {
continue; /* 忽略未使用的转换器 */
}
}
else {
// 如果 usecols 为 NULL,则处理列索引的边界情况
if (column < -num_fields || column >= num_fields) {
PyErr_Format(PyExc_ValueError,
"converter specified for column %zd, which is invalid "
"for the number of fields %zd.", column, num_fields);
goto error;
}
if (column < 0) {
column += num_fields;
}
}
if (!PyCallable_Check(value)) {
// 检查 value 是否可调用,如果不是则报错
PyErr_Format(PyExc_TypeError,
"values of the converters dictionary must be callable, "
"but the value associated with key %R is not", key);
goto error;
}
Py_INCREF(value);
// 增加 value 的引用计数,确保其在后续使用过程中不会被释放
conv_funcs[column] = value;
// 将 value 存入 conv_funcs 数组对应的列索引位置
}
return conv_funcs;
error:
// 处理错误时的清理工作
for (Py_ssize_t i = 0; i < num_fields; i++) {
Py_XDECREF(conv_funcs[i]);
// 逐个释放 conv_funcs 数组中的引用
}
PyMem_FREE(conv_funcs);
// 释放 conv_funcs 数组的内存空间
return NULL;
}
/**
* Read a file into the provided array, or create (and possibly grow) an
* array to read into.
*
* @param s The stream object/struct providing reading capabilities used by
* the tokenizer.
* @param max_rows The number of rows to read, or -1. If negative
* all rows are read.
* @param num_field_types The number of field types stored in `field_types`.
* @param field_types Information about the dtype for each column (or one if
* `homogeneous`).
* @param pconfig Pointer to the parser config object used by both the
* tokenizer and the conversion functions.
* @param num_usecols The number of columns in `usecols`.
* @param usecols An array of length `num_usecols` or NULL. If given indicates
* which column is read for each individual row (negative columns are
* accepted).
* @param skiplines The number of lines to skip, these lines are ignored.
* @param converters Python dictionary of converters. Finalizing converters
* is difficult without information about the number of columns.
* @param data_array An array to be filled or NULL. In either case a new
* reference is returned (the reference to `data_array` is not stolen).
* @param out_descr The dtype used for allocating a new array. This is not
* used if `data_array` is provided. Note that the actual dtype of the
* returned array can differ for strings.
* @param num_cols Pointer in which the actual (discovered) number of columns
* is returned. This is only relevant if `homogeneous` is true.
* @param homogeneous Whether the datatype of the array is not homogeneous,
* i.e. not structured. In this case the number of columns has to be
* discovered an the returned array will be 2-dimensional rather than
* 1-dimensional.
*
* @returns Returns the result as an array object or NULL on error. The result
* is always a new reference (even when `data_array` was passed in).
*/
NPY_NO_EXPORT PyArrayObject *
read_rows(stream *s,
npy_intp max_rows, Py_ssize_t num_field_types, field_type *field_types,
parser_config *pconfig, Py_ssize_t num_usecols, Py_ssize_t *usecols,
Py_ssize_t skiplines, PyObject *converters,
PyArrayObject *data_array, PyArray_Descr *out_descr,
bool homogeneous)
{
// 指向数据的指针,初始化为 NULL
char *data_ptr = NULL;
// 当前字段的数量
Py_ssize_t current_num_fields;
// 行的大小,由输出描述符的元素大小决定
npy_intp row_size = out_descr->elsize;
// 是否需要初始化数据
bool needs_init = PyDataType_FLAGCHK(out_descr, NPY_NEEDS_INIT);
// 数组的维度,如果数据类型不同构,则为 2,否则为 1
int ndim = homogeneous ? 2 : 1;
// 结果数组的形状,初始为 {0, 1}
npy_intp result_shape[2] = {0, 1};
// 数据数组是否已分配的标志
bool data_array_allocated = data_array == NULL;
/* 确保我们对错误处理目的拥有 `data_array` 的所有权 */
// 增加 `data_array` 的引用计数
Py_XINCREF(data_array);
// 每块中的行数,根据行大小而增加
size_t rows_per_block = 1;
// 已分配的数据行数,初始为 0
npy_intp data_allocated_rows = 0;
/* 如果 max_rows 被使用并且遇到空行,则发出警告 */
bool give_empty_row_warning = max_rows >= 0;
int ts_result = 0;
tokenizer_state ts;
if (npy_tokenizer_init(&ts, pconfig) < 0) {
goto error;
}
/* 如果已知字段数量,设置实际字段数;否则设置为 -1 */
Py_ssize_t actual_num_fields = -1;
if (usecols != NULL) {
assert(homogeneous || num_field_types == num_usecols);
actual_num_fields = num_usecols;
}
else if (!homogeneous) {
assert(usecols == NULL || num_field_types == num_usecols);
actual_num_fields = num_field_types;
}
for (Py_ssize_t i = 0; i < skiplines; i++) {
ts.state = TOKENIZE_GOTO_LINE_END;
ts_result = npy_tokenize(s, &ts, pconfig);
if (ts_result < 0) {
goto error;
}
else if (ts_result != 0) {
/* 少于指定行数是可以接受的 */
break;
}
}
Py_ssize_t row_count = 0; /* 实际处理的行数 */
npy_tokenizer_clear(&ts);
if (conv_funcs != NULL) {
for (Py_ssize_t i = 0; i < actual_num_fields; i++) {
Py_XDECREF(conv_funcs[i]);
}
PyMem_FREE(conv_funcs);
}
if (data_array == NULL) {
assert(row_count == 0 && result_shape[0] == 0);
if (actual_num_fields == -1) {
/*
* 如果找不到行数,则必须猜测有一个元素
* 注意:可以考虑将此移到外部以优化必要的行为
*/
result_shape[1] = 1;
}
else {
result_shape[1] = actual_num_fields;
}
Py_INCREF(out_descr);
data_array = (PyArrayObject *)PyArray_Empty(
ndim, result_shape, out_descr, 0);
}
/*
* 注意,如果没有数据,`data_array` 可能仍为 NULL,row_count 为 0。
* 在这种情况下,始终重新分配以防万一。
*/
if (data_array_allocated && data_allocated_rows != row_count) {
size_t size = row_count * row_size;
char *new_data = PyDataMem_UserRENEW(
PyArray_BYTES(data_array), size ? size : 1,
PyArray_HANDLER(data_array));
if (new_data == NULL) {
Py_DECREF(data_array);
PyErr_NoMemory();
return NULL;
}
((PyArrayObject_fields *)data_array)->data = new_data;
((PyArrayObject_fields *)data_array)->dimensions[0] = row_count;
}
return data_array;
error:
if (conv_funcs != NULL) {
for (Py_ssize_t i = 0; i < actual_num_fields; i++) {
Py_XDECREF(conv_funcs[i]);
}
PyMem_FREE(conv_funcs);
}
npy_tokenizer_clear(&ts);
Py_XDECREF(data_array);
return NULL;
}
.\numpy\numpy\_core\src\multiarray\textreading\rows.h
// 定义了一个条件编译的预处理指令,用于避免重复包含同一文件
// 如果未定义 NUMPY_CORE_SRC_MULTIARRAY_TEXTREADING_ROWS_H_,则进行以下处理
// 防止多次包含同一头文件造成的编译错误
// 清除旧的 Py_ssize_t 定义,使用更安全的版本
// 包含 Python 的头文件,提供 Python C API 的支持
// 标准输入输出的头文件
// 包含文本读取的流处理头文件
// 包含字段类型定义的头文件
// 包含解析器配置的头文件
// 声明一个不导出的函数,返回 PyArrayObject 指针
NPY_NO_EXPORT PyArrayObject *
read_rows(stream *s,
npy_intp nrows, Py_ssize_t num_field_types, field_type *field_types,
parser_config *pconfig, Py_ssize_t num_usecols, Py_ssize_t *usecols,
Py_ssize_t skiplines, PyObject *converters,
PyArrayObject *data_array, PyArray_Descr *out_descr,
bool homogeneous);
// 函数原型声明,用于读取文本行数据并返回一个 NumPy 数组对象
.\numpy\numpy\_core\src\multiarray\textreading\stream.h
extern "C" {
/*
* When getting the next line, we hope that the buffer provider can already
* give some information about the newlines, because for Python iterables
* we definitely expect to get line-by-line buffers.
*
* BUFFER_IS_FILEEND must be returned when the end of the file is reached and
* must NOT be returned together with a valid (non-empty) buffer.
*/
// 定义常量:缓冲区可能包含换行符
// 定义常量:缓冲区是行结束
// 定义常量:缓冲区是文件结束
/*
* Base struct for streams. We currently have two, a chunked reader for
* filelikes and a line-by-line for any iterable.
* As of writing, the chunked reader was only used for filelikes not already
* opened. That is to preserve the amount read in case of an error exactly.
* If we drop this, we could read it more often (but not when `max_rows` is
* used).
*
* The "streams" can extend this struct to store their own data (so it is
* a very lightweight "object").
*/
// 定义流的基本结构体,用于处理不同类型的流
typedef struct _stream {
// 函数指针,用于获取下一个缓冲区
int (*stream_nextbuf)(void *sdata, char **start, char **end, int *kind);
// 函数指针,用于关闭流
int (*stream_close)(struct _stream *strm);
} stream;
// 宏定义:通过函数指针调用stream_nextbuf函数
((s)->stream_nextbuf((s), start, end, kind))
// 宏定义:通过函数指针调用stream_close函数
}
这段代码是一个 C 语言的头文件,定义了用于流处理的基本结构体和相关的宏定义。
.\numpy\numpy\_core\src\multiarray\textreading\stream_pyobject.c
/*
* C side structures to provide capabilities to read Python file like objects
* in chunks, or iterate through iterables with each result representing a
* single line of a file.
*/
/*
* Cleans the typedef before including Python.h to avoid potential conflicts
* with legacy code.
*/
/*
* Includes necessary headers for standard I/O operations and memory allocation.
*/
/*
* Ensures compatibility with the latest NumPy API.
*/
/*
* Includes custom header for stream operations related to text reading.
*/
/*
* Defines the chunk size for reading operations.
*/
/*
* Structure representing a file reading context in Python with chunked
* operations.
*/
typedef struct {
stream stream; /* Custom stream structure */
PyObject *file; /* Python file object being read */
PyObject *read; /* `read` attribute of the file object */
PyObject *chunksize; /* Amount to read each time from `obj.read()` */
PyObject *chunk; /* Most recently read line from the file */
const char *encoding; /* Encoding compatible with PyUnicode_Encode */
} python_chunks_from_file;
/*
* Helper function to process Python string-like objects, supporting both
* byte objects and Unicode strings.
*
* NOTE: Steals a reference to `str` (although usually returns it unmodified).
*/
static inline PyObject *
process_stringlike(PyObject *str, const char *encoding)
{
if (PyBytes_Check(str)) {
PyObject *ustr;
/* Converts byte object to Unicode using specified encoding */
ustr = PyUnicode_FromEncodedObject(str, encoding, NULL);
if (ustr == NULL) {
return NULL;
}
Py_DECREF(str);
return ustr;
}
else if (!PyUnicode_Check(str)) {
PyErr_SetString(PyExc_TypeError,
"non-string returned while reading data");
Py_DECREF(str);
return NULL;
}
return str;
}
/*
* Retrieves buffer information from a Unicode string object, setting
* start and end pointers and kind of Unicode data (1-byte, 2-byte, or 4-byte).
*/
static inline void
buffer_info_from_unicode(PyObject *str, char **start, char **end, int *kind)
{
Py_ssize_t length = PyUnicode_GET_LENGTH(str);
*kind = PyUnicode_KIND(str);
/* Determines the type of Unicode data and sets appropriate pointers */
if (*kind == PyUnicode_1BYTE_KIND) {
*start = (char *)PyUnicode_1BYTE_DATA(str);
}
else if (*kind == PyUnicode_2BYTE_KIND) {
*start = (char *)PyUnicode_2BYTE_DATA(str);
length *= sizeof(Py_UCS2);
}
else if (*kind == PyUnicode_4BYTE_KIND) {
*start = (char *)PyUnicode_4BYTE_DATA(str);
length *= sizeof(Py_UCS4);
}
*end = *start + length;
}
/*
* Retrieves the next buffer from the Python file object in chunks,
* updating start and end pointers and kind of Unicode data.
*/
static int
fb_nextbuf(python_chunks_from_file *fb, char **start, char **end, int *kind)
{
/* Release any previous chunk read */
Py_XDECREF(fb->chunk);
fb->chunk = NULL;
/* Calls `read` method of the file object to fetch the next chunk */
PyObject *chunk = PyObject_CallFunctionObjArgs(fb->read, fb->chunksize, NULL);
if (chunk == NULL) {
return -1; /* Returns error if unable to read */
}
/* Processes the retrieved chunk as a string-like object */
fb->chunk = process_stringlike(chunk, fb->encoding);
if (fb->chunk == NULL) {
return -1; /* Returns error if processing fails */
}
/* Retrieves buffer information from the processed Unicode chunk */
buffer_info_from_unicode(fb->chunk, start, end, kind);
if (*start == *end) {
return BUFFER_IS_FILEEND; /* Indicates end of file */
}
return BUFFER_MAY_CONTAIN_NEWLINE; /* Indicates more data available */
}
/*
* Deletes the allocated resources associated with the file reading context.
*/
static int
fb_del(stream *strm)
{
python_chunks_from_file *fb = (python_chunks_from_file *)strm;
/* Releases Python objects and memory allocated for the context */
Py_XDECREF(fb->file);
Py_XDECREF(fb->read);
Py_XDECREF(fb->chunksize);
Py_XDECREF(fb->chunk);
/* Frees the memory allocated for the stream structure */
PyMem_FREE(strm);
/* Returns success status */
return 0;
}
return 0;
/*
* 从 Python 文件对象中创建流
*/
NPY_NO_EXPORT stream *
stream_python_file(PyObject *obj, const char *encoding)
{
// 分配内存以存储 python_chunks_from_file 结构体
python_chunks_from_file *fb;
// 使用 PyMem_Calloc 分配内存,如果失败则抛出内存错误并返回空
fb = (python_chunks_from_file *)PyMem_Calloc(1, sizeof(python_chunks_from_file));
if (fb == NULL) {
PyErr_NoMemory();
return NULL;
}
// 设置流的下一个缓冲区函数为 fb_nextbuf
fb->stream.stream_nextbuf = (void *)&fb_nextbuf;
// 设置流的关闭函数为 fb_del
fb->stream.stream_close = &fb_del;
// 设置编码方式
fb->encoding = encoding;
// 增加 Python 文件对象的引用计数
Py_INCREF(obj);
// 将 Python 文件对象赋值给结构体中的 file 成员
fb->file = obj;
// 获取 Python 文件对象的 read 方法
fb->read = PyObject_GetAttrString(obj, "read");
if (fb->read == NULL) {
// 如果获取失败,则跳转到失败标签进行清理并返回空
goto fail;
}
// 设置 chunksize 为 READ_CHUNKSIZE 的 Python 长整型对象
fb->chunksize = PyLong_FromLong(READ_CHUNKSIZE);
if (fb->chunksize == NULL) {
// 如果创建失败,则跳转到失败标签进行清理并返回空
goto fail;
}
// 成功则返回文件流结构体的指针转换为流类型的指针
return (stream *)fb;
fail:
// 失败时清理结构体内存并返回空
fb_del((stream *)fb);
return NULL;
}
/*
* 从 Python 可迭代对象中创建流,将每个项目解释为文件中的一行
*/
typedef struct {
stream stream;
// 正在读取的 Python 文件对象
PyObject *iterator;
// 最近获取的 Python str 对象行
PyObject *line;
// 与 Python 的 PyUnicode_Encode 兼容的编码(可以为空)
const char *encoding;
} python_lines_from_iterator;
/*
* 清理迭代器流
*/
static int
it_del(stream *strm)
{
// 将流类型强制转换为 python_lines_from_iterator 类型
python_lines_from_iterator *it = (python_lines_from_iterator *)strm;
// 释放迭代器和行对象的引用
Py_XDECREF(it->iterator);
Py_XDECREF(it->line);
// 释放流的内存
PyMem_FREE(strm);
return 0;
}
/*
* 获取下一个缓冲区的数据
*/
static int
it_nextbuf(python_lines_from_iterator *it, char **start, char **end, int *kind)
{
// 清理之前的行对象引用
Py_XDECREF(it->line);
it->line = NULL;
// 从迭代器获取下一行对象
PyObject *line = PyIter_Next(it->iterator);
if (line == NULL) {
// 如果获取失败且有错误发生,则返回 -1
if (PyErr_Occurred()) {
return -1;
}
// 否则设置开始和结束为 NULL,表示文件结束
*start = NULL;
*end = NULL;
return BUFFER_IS_FILEEND;
}
// 处理行对象,并使用给定编码转换
it->line = process_stringlike(line, it->encoding);
if (it->line == NULL) {
return -1;
}
// 将 Unicode 编码的字符串转换为缓冲区信息
buffer_info_from_unicode(it->line, start, end, kind);
return BUFFER_IS_LINEND;
}
/*
* 从 Python 可迭代对象中创建流
*/
NPY_NO_EXPORT stream *
stream_python_iterable(PyObject *obj, const char *encoding)
{
// 分配内存以存储 python_lines_from_iterator 结构体
python_lines_from_iterator *it;
// 检查对象是否为可迭代对象,否则设置类型错误并返回空
if (!PyIter_Check(obj)) {
PyErr_SetString(PyExc_TypeError,
"error reading from object, expected an iterable.");
return NULL;
}
// 分配内存以存储迭代器流结构体
it = (python_lines_from_iterator *)PyMem_Calloc(1, sizeof(*it));
if (it == NULL) {
PyErr_NoMemory();
return NULL;
}
// 设置流的下一个缓冲区函数为 it_nextbuf
it->stream.stream_nextbuf = (void *)&it_nextbuf;
// 设置流的关闭函数为 it_del
it->stream.stream_close = &it_del;
// 设置编码方式
it->encoding = encoding;
// 增加 Python 可迭代对象的引用计数
Py_INCREF(obj);
// 将 Python 可迭代对象赋值给结构体中的 iterator 成员
it->iterator = obj;
// 成功则返回可迭代器流结构体的指针转换为流类型的指针
return (stream *)it;
}
.\numpy\numpy\_core\src\multiarray\textreading\stream_pyobject.h
// 定义了一个宏,用于确保 Py_ssize_t 类型在包含 Python.h 之前被定义
// 包含 Python.h 头文件,这是 Python C API 的主要头文件
// 包含自定义的 stream 头文件,用于处理流的操作
// 声明一个不导出的函数,该函数将 Python 对象转换为 stream 对象,使用指定的编码
NPY_NO_EXPORT stream *
stream_python_file(PyObject *obj, const char *encoding);
// 声明一个不导出的函数,该函数将 Python 可迭代对象转换为 stream 对象,使用指定的编码
NPY_NO_EXPORT stream *
stream_python_iterable(PyObject *obj, const char *encoding);
// 结束对头文件的声明
.\numpy\numpy\_core\src\multiarray\textreading\str_to_int.c
const char *deprecation_msg = (
"loadtxt(): Parsing an integer via a float is deprecated. To avoid "
"this warning, you can:\n"
" * make sure the original data is stored as integers.\n"
" * use the `converters=` keyword argument. If you only use\n"
" NumPy 1.23 or later, `converters=float` will normally work.\n"
" * Use `np.loadtxt(...).astype(np.int64)` parsing the file as\n"
" floating point and then convert it. (On all NumPy versions.)\n"
" (Deprecated NumPy 1.23)");
NPY_NO_EXPORT int \
npy_to_
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr, \
parser_config *pconfig) \
NPY_NO_EXPORT int \
npy_to_
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr, \
parser_config *pconfig) \
const char *deprecation_msg = (
"loadtxt(): Parsing an integer via a float is deprecated. To avoid "
"this warning, you can:\n"
" * make sure the original data is stored as integers.\n"
" * use the `converters=` keyword argument. If you only use\n"
" NumPy 1.23 or later, `converters=float` will normally work.\n"
" * Use `np.loadtxt(...).astype(np.int64)` parsing the file as\n"
" floating point and then convert it. (On all NumPy versions.)\n"
" (Deprecated NumPy 1.23)");
NPY_NO_EXPORT int \
npy_to_
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr, \
parser_config *pconfig) \
NPY_NO_EXPORT int \
npy_to_
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr, \
parser_config *pconfig) \
{
// 声明一个变量用于存储解析后的整数值
int64_t parsed;
// 声明一个变量 x,用于存储最终转换后的整数值,类型为 intw
intw
// 检查字符串转换为整数是否失败
if (NPY_UNLIKELY(
str_to_int64(str, end, INT_MIN, INT_MAX, &parsed) < 0)) {
/* DEPRECATED 2022-07-03, NumPy 1.23 */
// 如果字符串转换为整数失败,则尝试将其解析为浮点数
double fval;
// 创建一个双精度浮点数的 NumPy 数组描述符对象
PyArray_Descr *d_descr = PyArray_DescrFromType(NPY_DOUBLE);
Py_DECREF(d_descr); /* borrowed */
// 如果将字符串转换为浮点数失败,则返回错误
if (npy_to_double(d_descr, str, end, (char *)&fval, pconfig) < 0) {
return -1;
}
// 如果之前没有发出浮点数转整数的警告,则发出警告
if (!pconfig->gave_int_via_float_warning) {
pconfig->gave_int_via_float_warning = true;
// 发出 DeprecationWarning,指明相关信息
if (PyErr_WarnEx(PyExc_DeprecationWarning,
deprecation_msg, 3) < 0) {
return -1;
}
}
pconfig->gave_int_via_float_warning = true;
// 将浮点数转换为 intw
x = (intw
}
else {
// 如果字符串成功转换为整数,则直接赋值给变量 x
x = (intw
}
// 将 x 的值复制到 dataptr 指向的内存中
memcpy(dataptr, &x, sizeof(x));
// 如果描述符的字节顺序不是本地字节顺序,则进行字节交换
if (!PyArray_ISNBO(descr->byteorder)) {
byteswap_unaligned(dataptr);
}
// 返回操作成功的标志
return 0;
}
/*
NPY_NO_EXPORT int \
npy_to_
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr, \
parser_config *pconfig) \
{ \
uint64_t parsed; \
uintw
\
if (NPY_UNLIKELY( \
str_to_uint64(str, end, UINT_MAX, &parsed) < 0)) { \
// DEPRECATED 2022-07-03, NumPy 1.23 \
double fval; \
PyArray_Descr *d_descr = PyArray_DescrFromType(NPY_DOUBLE); \
Py_DECREF(d_descr); /* borrowed */ \
if (npy_to_double(d_descr, str, end, (char *)&fval, pconfig) < 0) { \
return -1; \
} \
if (!pconfig->gave_int_via_float_warning) { \
pconfig->gave_int_via_float_warning = true; \
if (PyErr_WarnEx(PyExc_DeprecationWarning, \
deprecation_msg, 3) < 0) { \
return -1; \
} \
} \
pconfig->gave_int_via_float_warning = true; \
x = (uintw
} \
else { \
x = (uintw
} \
memcpy(dataptr, &x, sizeof(x)); \
if (!PyArray_ISNBO(descr->byteorder)) { \
byteswap_unaligned(dataptr); \
} \
return 0; \
}
*/
/*
*/
// 定义一个将字符串转换为指定类型整数的函数
DECLARE_TO_INT(int8, INT8_MIN, INT8_MAX, byteswap_nothing)
// 定义一个将字符串转换为16位有符号整数的函数,包括字节交换
DECLARE_TO_INT(int16, INT16_MIN, INT16_MAX, npy_bswap2_unaligned)
DECLARE_TO_INT(int32, INT32_MIN, INT32_MAX, npy_bswap4_unaligned)
DECLARE_TO_INT(int64, INT64_MIN, INT64_MAX, npy_bswap8_unaligned)
DECLARE_TO_UINT(uint8, UINT8_MAX, byteswap_nothing)
DECLARE_TO_UINT(uint16, UINT16_MAX, npy_bswap2_unaligned)
DECLARE_TO_UINT(uint32, UINT32_MAX, npy_bswap4_unaligned)
DECLARE_TO_UINT(uint64, UINT64_MAX, npy_bswap8_unaligned)
.\numpy\numpy\_core\src\multiarray\textreading\str_to_int.h
/*
* 定义头文件防止重复包含
*/
/*
* 定义宏以避免使用已废弃的 NumPy API,并指定使用的 API 版本
*/
/*
* 定义宏 _MULTIARRAYMODULE,用于标识本模块
*/
/*
* 包含 NumPy 数组数据类型相关的头文件
*/
/*
* 包含文本解析器配置文件的头文件
*/
/*
* 下面的两个字符串转换函数在 Pandas 中基本等效。
* 它们在这里的头文件中定义,以确保它们可以在其他函数中轻松内联。
* 与 Pandas 不同,传入结束指针(不依赖于 \0)并返回 0 或 -1。
*
* 实际函数在下面的宏模板定义中。
*/
/*
* 将字符串转换为 int64_t 类型的整数。
*/
NPY_FINLINE int
str_to_int64(
const Py_UCS4 *p_item, const Py_UCS4 *p_end,
int64_t int_min, int64_t int_max, int64_t *result)
{
const Py_UCS4 *p = (const Py_UCS4 *)p_item;
bool isneg = 0;
int64_t number = 0;
// 跳过前导空格。
while (Py_UNICODE_ISSPACE(*p)) {
++p;
}
// 处理符号。
if (*p == '-') {
isneg = true;
++p;
}
else if (*p == '+') {
p++;
}
// 检查是否有第一个数字。
if (!isdigit(*p)) {
return -1;
}
if (isneg) {
// 如果数字大于 pre_min,至少还可以处理一个更多的数字而不会溢出。
int dig_pre_min = -(int_min % 10);
int64_t pre_min = int_min / 10;
// 处理数字。
int d = *p;
while (isdigit(d)) {
if ((number > pre_min) || ((number == pre_min) && (d - '0' <= dig_pre_min))) {
number = number * 10 - (d - '0');
d = *++p;
}
else {
return -1;
}
}
}
else {
// 如果数字小于 pre_max,至少还可以处理一个更多的数字而不会溢出。
int64_t pre_max = int_max / 10;
int dig_pre_max = int_max % 10;
// 处理数字。
int d = *p;
while (isdigit(d)) {
if ((number < pre_max) || ((number == pre_max) && (d - '0' <= dig_pre_max))) {
number = number * 10 + (d - '0');
d = *++p;
}
else {
return -1;
}
}
}
// 跳过尾随空格。
while (Py_UNICODE_ISSPACE(*p)) {
++p;
}
// 是否使用了所有字符?
if (p != p_end) {
return -1;
}
// 将结果存入 result,并返回 0 表示成功。
*result = number;
return 0;
}
/*
* 将字符串转换为 uint64_t 类型的无符号整数。
*/
NPY_FINLINE int
str_to_uint64(
const Py_UCS4 *p_item, const Py_UCS4 *p_end,
uint64_t uint_max, uint64_t *result)
{
const Py_UCS4 *p = (const Py_UCS4 *)p_item;
uint64_t number = 0;
int d;
// 跳过前导空格。
while (Py_UNICODE_ISSPACE(*p)) {
++p;
}
// 处理符号。
if (*p == '-') {
return -1;
}
if (*p == '+') {
p++;
}
// 检查是否有第一个数字。
if (!isdigit(*p)) {
return -1;
}
// 如果数字小于 pre_max,意味着还可以处理至少一个更多的数字而不会溢出。
uint64_t pre_max = uint_max / 10;
int dig_pre_max = uint_max % 10;
// 处理数字。
d = *p;
while (isdigit(d)) {
// 如果满足条件:数字小于 pre_max,或者数字等于 pre_max 且当前数字不超过 dig_pre_max,则继续处理。
if ((number < pre_max) || ((number == pre_max) && (d - '0' <= dig_pre_max))) {
number = number * 10 + (d - '0');
d = *++p;
}
else {
return -1; // 如果超过了预设的最大值范围,返回错误。
}
}
// 跳过尾部的空格。
while (Py_UNICODE_ISSPACE(*p)) {
++p;
}
// 是否已经使用了所有的字符?
if (p != p_end) {
return -1; // 如果没有使用完所有字符,返回错误。
}
*result = number; // 将解析出的数字存入 result 指针指向的位置。
return 0; // 返回成功状态。
// 如果 NPY_NO_EXPORT 未定义,则定义为 int
NPY_NO_EXPORT int \
// 否则,空行
// 定义一个函数原型 npy_to_
npy_to_
const Py_UCS4 *str, const Py_UCS4 *end, char *dataptr, \
parser_config *pconfig);
// 使用宏 DECLARE_TO_INT_PROTOTYPE 分别声明以下整型转换函数的原型:
DECLARE_TO_INT_PROTOTYPE(int8)
DECLARE_TO_INT_PROTOTYPE(int16)
DECLARE_TO_INT_PROTOTYPE(int32)
DECLARE_TO_INT_PROTOTYPE(int64)
DECLARE_TO_INT_PROTOTYPE(uint8)
DECLARE_TO_INT_PROTOTYPE(uint16)
DECLARE_TO_INT_PROTOTYPE(uint32)
DECLARE_TO_INT_PROTOTYPE(uint64)
.\numpy\numpy\_core\src\multiarray\textreading\tokenize.cpp
/*
How parsing quoted fields works:
For quoting to be activated, the first character of the field
must be the quote character (after taking into account
ignore_leading_spaces). While quoting is active, delimiters
are treated as regular characters, not delimiters. Quoting is
deactivated by the second occurrence of the quote character. An
exception is the occurrence of two consecutive quote characters,
which is treated as a literal occurrence of a single quote character.
E.g. (with delimiter=',' and quote='"'):
12.3,"New York, NY","3'2"""
The second and third fields are `New York, NY` and `3'2"`.
If a non-delimiter occurs after the closing quote, the quote is
ignored and parsing continues with quoting deactivated. Quotes
that occur while quoting is not activated are not handled specially;
they become part of the data.
E.g:
12.3,"ABC"DEF,XY"Z
The second and third fields are `ABCDEF` and `XY"Z`.
Note that the second field of
12.3,"ABC" ,4.5
is `ABC `. Currently there is no option to ignore whitespace
at the end of a field.
*/
template <typename UCS>
static inline int
copy_to_field_buffer(tokenizer_state *ts,
const UCS *chunk_start, const UCS *chunk_end)
{
// 计算块的长度
npy_intp chunk_length = chunk_end - chunk_start;
// 计算需要的内存大小,包括长度和终止符,以及额外的空间
npy_intp size = chunk_length + ts->field_buffer_pos + 3;
// 如果当前分配的缓冲区不足以容纳新的数据
if (NPY_UNLIKELY(ts->field_buffer_length < size)) {
// 计算需要增长的内存大小
npy_intp alloc_size = grow_size_and_multiply(&size, 32, sizeof(Py_UCS4));
// 如果增长大小为负数,表示无法处理如此长的行
if (alloc_size < 0) {
PyErr_Format(PyExc_ValueError,
"line too long to handle while reading file.");
return -1;
}
// 重新分配内存
Py_UCS4 *grown = (Py_UCS4 *)PyMem_Realloc(ts->field_buffer, alloc_size);
// 如果分配失败
if (grown == nullptr) {
PyErr_NoMemory();
return -1;
}
// 更新缓冲区长度和指针
ts->field_buffer_length = size;
ts->field_buffer = grown;
}
// 将块数据复制到字段缓冲区中
Py_UCS4 *write_pos = ts->field_buffer + ts->field_buffer_pos;
for (; chunk_start < chunk_end; chunk_start++, write_pos++) {
*write_pos = (Py_UCS4)*chunk_start;
}
// 确保以NUL结尾
*write_pos = '\0';
// 更新字段缓冲区位置
ts->field_buffer_pos += chunk_length;
return 0;
}
static inline int
add_field(tokenizer_state *ts)
{
// 上一个字段已完成,增加一个NUL字节作为结束符
ts->field_buffer_pos += 1;
# 检查字段数量是否超过当前数组大小,如果是则需要扩展数组
if (NPY_UNLIKELY(ts->num_fields + 1 > ts->fields_size)) {
# 记录当前字段数量
npy_intp size = ts->num_fields;
# 计算需要分配的新数组大小
npy_intp alloc_size = grow_size_and_multiply(
&size, 4, sizeof(field_info));
# 检查分配大小是否小于0,通常情况下不可能发生
if (alloc_size < 0) {
# 报错,提示列数过多,无法读取文件
PyErr_Format(PyExc_ValueError,
"too many columns found; cannot read file.");
return -1;
}
# 重新分配内存以扩展字段数组
field_info *fields = (field_info *)PyMem_Realloc(ts->fields, alloc_size);
# 检查内存重新分配是否成功
if (fields == nullptr) {
# 内存分配失败,抛出内存错误异常
PyErr_NoMemory();
return -1;
}
# 更新字段数组和大小
ts->fields = fields;
ts->fields_size = size;
}
# 设置当前字段的偏移量和引用
ts->fields[ts->num_fields].offset = ts->field_buffer_pos;
ts->fields[ts->num_fields].quoted = false;
# 增加字段数量
ts->num_fields += 1;
# 确保当前字段缓冲区的末尾是空字符
/* Ensure this (currently empty) word is NUL terminated. */
ts->field_buffer[ts->field_buffer_pos] = '\0';
# 断言当前字段缓冲区长度大于字段位置,确保位置正确
assert(ts->field_buffer_length > ts->field_buffer_pos);
# 返回操作成功
return 0;
}
/*
* tokenizer_core 函数负责实现通用的分词器逻辑,用于处理特定 UCS 类型的文本流
* 参数 ts: 分词器状态结构体指针,用于保存分词器的状态信息
* 参数 config: 解析器配置结构体指针,包含配置信息,如是否忽略前导空白字符和引号类型
*/
template <typename UCS>
static inline int
tokenizer_core(tokenizer_state *ts, parser_config *const config)
{
// 将指针 pos 设置为当前位置 ts->pos 的 UCS 类型指针
UCS *pos = (UCS *)ts->pos;
// 将指针 stop 设置为结束位置 ts->end 的 UCS 类型指针
UCS *stop = (UCS *)ts->end;
UCS *chunk_start;
// 如果分词器的状态为 TOKENIZE_CHECK_QUOTED
if (ts->state == TOKENIZE_CHECK_QUOTED) {
/* before we can check for quotes, strip leading whitespace */
// 如果配置要求忽略前导空白字符
if (config->ignore_leading_whitespace) {
// 跳过前导空白字符,直到遇到非空白字符、'\r' 或 '\n'
while (pos < stop && Py_UNICODE_ISSPACE(*pos) &&
*pos != '\r' && *pos != '\n') {
pos++;
}
// 如果已经到达结束位置,则更新分词器状态并返回
if (pos == stop) {
ts->pos = (char *)pos;
return 0;
}
}
/* Setting chunk effectively starts the field */
// 如果当前位置的字符为配置中指定的引号字符
if (*pos == config->quote) {
// 标记当前字段为带引号的字段,并设置分词器状态为 TOKENIZE_QUOTED
ts->fields[ts->num_fields - 1].quoted = true;
ts->state = TOKENIZE_QUOTED;
// 移动 pos 指针到下一个位置,即跳过引号字符
pos++; /* TOKENIZE_QUOTED is OK with pos == stop */
}
else {
/* Set to TOKENIZE_QUOTED or TOKENIZE_QUOTED_WHITESPACE */
// 否则根据当前未引用的状态设置分词器状态
ts->state = ts->unquoted_state;
}
}
// 更新分词器位置指针为当前 pos 指针位置的字符指针
ts->pos = (char *)pos;
// 返回 0 表示处理完毕
return 0;
}
/*
* 此分词器总是复制完整的 "row"(所有标记)。这样做有两个好处:
* 1. 确保每个单词后面都有一个 NUL 字符(尽管它也可能包含一个)。
* 2. 如果使用了 usecols,我们可以通过完全解析第一行更轻松地嗅探出它。此外,usecols 可能是负数,因此我们可能事先不知道需要哪一行。
*
* 分词器可以增加跳过字段和在已知情况下检查最大字段数的能力,目前还不清楚这是否值得做。
*
* 与一些分词器不同,此分词器尝试按块工作并按块复制数据。希望这样做可以多次轻量循环,而不是单个重型循环,例如快速扫描字段的结束。复制块还意味着我们通常只需检查一次缓冲区是否足够大。
* 不同的选择是可能的,这个选择似乎效果不错。
*
* 分词器的核心部分为三种 Python Unicode 类型 UCS1、UCS2 和 UCS4 进行了专门优化。
*/
NPY_NO_EXPORT int
npy_tokenize(stream *s, tokenizer_state *ts, parser_config *const config)
{
// 断言确保字段大小至少为 2
assert(ts->fields_size >= 2);
// 断言确保字段缓冲区长度至少为两倍 UCS4 的大小
assert(ts->field_buffer_length >= 2*(Py_ssize_t)sizeof(Py_UCS4));
// 标记文件读取是否完成的标志,初始为 0 表示未完成
int finished_reading_file = 0;
/* Reset to start of buffer */
// 重置字段缓冲区位置为起始位置
ts->field_buffer_pos = 0;
// 重置字段数量为 0
ts->num_fields = 0;
}
while (true) {
/*
* This loop adds new fields to the result (to make up a full row)
* until the row ends (typically a line end or the file end)
*/
// 如果当前状态为TOKENIZE_INIT,表示需要开始一个新字段的处理
if (ts->state == TOKENIZE_INIT) {
/* Start a new field */
// 调用add_field函数开始一个新字段的处理,如果返回小于0则表示出错
if (add_field(ts) < 0) {
return -1;
}
// 设置状态为TOKENIZE_CHECK_QUOTED,表示需要检查是否为带引号的字段
ts->state = TOKENIZE_CHECK_QUOTED;
}
// 如果当前位置已经超过了缓冲区的末尾
if (NPY_UNLIKELY(ts->pos >= ts->end)) {
// 如果缓冲区状态为BUFFER_IS_LINEND并且不处于TOKENIZE_QUOTED状态,表示当前行已结束
if (ts->buf_state == BUFFER_IS_LINEND &&
ts->state != TOKENIZE_QUOTED) {
/*
* Finished line, do not read anymore (also do not eat \n).
* If we are in a quoted field and the "line" does not end with
* a newline, the quoted field will not have it either.
* I.e. `np.loadtxt(['"a', 'b"'], dtype="S2", quotechar='"')`
* reads "ab". This matches `next(csv.reader(['"a', 'b"']))`.
*/
// 如果在带引号的字段中,且行未以换行符结束,则带引号字段也不会包含换行符
break;
}
/* fetch new data */
// 获取新的数据块,并更新缓冲区状态
ts->buf_state = stream_nextbuf(s,
&ts->pos, &ts->end, &ts->unicode_kind);
// 如果获取数据出错,返回-1
if (ts->buf_state < 0) {
return -1;
}
// 如果已到达文件末尾
if (ts->buf_state == BUFFER_IS_FILEEND) {
finished_reading_file = 1;
ts->pos = ts->end; /* stream should ensure this. */
break;
}
// 如果当前位置等于缓冲区末尾,表示遇到空行
else if (ts->pos == ts->end) {
/* This must be an empty line (and it must be indicated!). */
assert(ts->buf_state == BUFFER_IS_LINEND);
break;
}
}
// 根据当前数据块的编码类型选择合适的分词器,并进行分词
int status;
if (ts->unicode_kind == PyUnicode_1BYTE_KIND) {
status = tokenizer_core<Py_UCS1>(ts, config);
}
else if (ts->unicode_kind == PyUnicode_2BYTE_KIND) {
status = tokenizer_core<Py_UCS2>(ts, config);
}
else {
assert(ts->unicode_kind == PyUnicode_4BYTE_KIND);
status = tokenizer_core<Py_UCS4>(ts, config);
}
// 如果分词过程中出错,返回-1
if (status < 0) {
return -1;
}
// 如果当前状态为TOKENIZE_LINE_END,表示已完成一行的分词
if (ts->state == TOKENIZE_LINE_END) {
break;
}
}
/*
* We have finished tokenizing a full row into fields, finalize result
*/
// 如果缓冲区状态为BUFFER_IS_LINEND,表示当前行已处理完毕
if (ts->buf_state == BUFFER_IS_LINEND) {
/* This line is "finished", make sure we don't touch it again: */
// 将缓冲区状态更新为BUFFER_MAY_CONTAIN_NEWLINE,避免再次处理当前行
ts->buf_state = BUFFER_MAY_CONTAIN_NEWLINE;
// 如果当前位置小于末尾,表示发现未引用的嵌入换行符,抛出异常
if (NPY_UNLIKELY(ts->pos < ts->end)) {
PyErr_SetString(PyExc_ValueError,
"Found an unquoted embedded newline within a single line of "
"input. This is currently not supported.");
return -1;
}
}
/* Finish the last field (we "append" one to store the last ones length) */
// 处理最后一个字段,添加长度信息
if (add_field(ts) < 0) {
return -1;
}
// 减去最后一个空字段
ts->num_fields -= 1;
}
/*
* 我们总是从新字段开始(从开头开始,以及每次找到分隔符时)。
* 这给了我们两种情况需要忽略最后一个字段如果它为空:
* 1. 如果恰好有一个空的(未引用的)字段,整行就是空的。
* 2. 如果我们在分割空白字符上,我们总是忽略最后一个空字段以匹配Python的分割行为:" 1 ".split()。
* (当我们只跳过行时,零个字段是可能的)
*/
if (ts->num_fields == 1 || (ts->num_fields > 0
&& ts->unquoted_state == TOKENIZE_UNQUOTED_WHITESPACE)) {
// 获取最后一个字段的偏移量和结束位置
size_t offset_last = ts->fields[ts->num_fields-1].offset;
size_t end_last = ts->fields[ts->num_fields].offset;
// 如果最后一个字段不是引用的且长度为1,则将其忽略
if (!ts->fields->quoted && end_last - offset_last == 1) {
ts->num_fields--; // 减少字段计数,忽略最后一个字段
}
}
ts->state = TOKENIZE_INIT; // 设置解析状态为初始状态
return finished_reading_file; // 返回文件读取完成状态
/*
* 清理 tokenizer_state 结构体中的资源,包括释放动态分配的内存和重置相关变量。
*/
NPY_NO_EXPORT void
npy_tokenizer_clear(tokenizer_state *ts)
{
// 释放 field_buffer 指针指向的内存,并将其置为 nullptr
PyMem_FREE(ts->field_buffer);
ts->field_buffer = nullptr;
// 释放 fields 指针指向的内存,并将其置为 nullptr,并重置 fields_size 为 0
PyMem_FREE(ts->fields);
ts->fields = nullptr;
ts->fields_size = 0;
}
/*
* 初始化 tokenizer_state 结构体,可能会将所有重要的配置变量复制到 tokenizer_state 中,
* 这样在进行 tokenizing 过程中可以提高缓存的局部性。
*/
NPY_NO_EXPORT int
npy_tokenizer_init(tokenizer_state *ts, parser_config *config)
{
/* 如果我们按行处理,state 和 buf_state 可能会移到 tokenize 函数中 */
// 初始化 buf_state 为 BUFFER_MAY_CONTAIN_NEWLINE
ts->buf_state = BUFFER_MAY_CONTAIN_NEWLINE;
// 初始化 state 为 TOKENIZE_INIT
ts->state = TOKENIZE_INIT;
// 根据配置设置 unquoted_state,如果 delimiter_is_whitespace 为真,则设置为 TOKENIZE_UNQUOTED_WHITESPACE,否则设置为 TOKENIZE_UNQUOTED
if (config->delimiter_is_whitespace) {
ts->unquoted_state = TOKENIZE_UNQUOTED_WHITESPACE;
}
else {
ts->unquoted_state = TOKENIZE_UNQUOTED;
}
// 初始化 num_fields 为 0
ts->num_fields = 0;
// 将 buf_state 重置为 0
ts->buf_state = 0;
// 初始化 pos 和 end 为 nullptr
ts->pos = nullptr;
ts->end = nullptr;
// 分配并初始化 field_buffer,长度为 32 个 Py_UCS4 字符
ts->field_buffer = (Py_UCS4 *)PyMem_Malloc(32 * sizeof(Py_UCS4));
if (ts->field_buffer == nullptr) {
PyErr_NoMemory();
return -1;
}
ts->field_buffer_length = 32;
// 分配并初始化 fields 数组,大小为 4 个 field_info 结构体大小
ts->fields = (field_info *)PyMem_Malloc(4 * sizeof(*ts->fields));
if (ts->fields == nullptr) {
// 分配失败时释放已分配的内存,返回内存错误
PyMem_Free(ts->field_buffer);
ts->field_buffer = nullptr;
PyErr_NoMemory();
return -1;
}
ts->fields_size = 4;
// 初始化成功,返回 0
return 0;
}