Alb*_* O. 8 python string cpython pep
我正在研究 PEP 393 之后 Python 如何表示字符串,但我不明白 PyASCIIObject 和 PyCompactUnicodeObject 之间的区别。
我的理解是字符串用以下结构表示:
typedef struct {
PyObject_HEAD
Py_ssize_t length; /* Number of code points in the string */
Py_hash_t hash; /* Hash value; -1 if not set */
struct {
unsigned int interned:2;
unsigned int kind:3;
unsigned int compact:1;
unsigned int ascii:1;
unsigned int ready:1;
unsigned int :24;
} state;
wchar_t *wstr; /* wchar_t representation (null-terminated) */
} PyASCIIObject;
typedef struct {
PyASCIIObject _base;
Py_ssize_t utf8_length;
char *utf8;
Py_ssize_t wstr_length;
} PyCompactUnicodeObject;
typedef struct {
PyCompactUnicodeObject _base;
union {
void *any;
Py_UCS1 *latin1;
Py_UCS2 *ucs2;
Py_UCS4 *ucs4;
} data;
} PyUnicodeObject;
Run Code Online (Sandbox Code Playgroud)
如果我错了,请纠正我,但我的理解是 PyASCIIObject 仅用于包含 ASCII 字符的字符串,PyCompactUnicodeObject 使用 PyASCIIObject 结构,它用于至少包含一个非 ASCII 字符的字符串,而 PyUnicodeObject 用于遗留函数。那是对的吗?
另外,为什么 PyASCIIObject 使用 wchar_t?字符不足以表示 ASCII 字符串吗?另外,如果 PyASCIIObject 已经有了 wchar_t 指针,为什么 PyCompactUnicodeObject 也有一个 char 指针呢?我的理解是两个指针都指向同一个位置,但是为什么要同时包含这两个指针?
PEP 373确实是您问题的最佳参考,尽管有时也需要C-API 文档。让我们一一解答您的问题:
你有正确的类型。但是有一个不明显的问题:当您使用任一“紧凑”类型(PyASCIIObject或PyCompactUnicodeObject)时,结构本身只是一个标题。字符串的实际数据立即存储在内存中的结构之后。数据使用的编码由kind字段描述,并将取决于字符串中的最大字符值。
前两个结构中的wstr和utf8指针是在 C 代码请求时可以存储转换后的表示的地方。对于 ASCII 字符串(使用PyASCIIObject),UTF-8 数据不需要缓存指针,因为 ASCII 数据本身与 UTF-8 兼容。宽字符缓存仅由不推荐使用的函数使用。
这两个缓存指针永远不会指向同一个地方,因为它们的类型不直接兼容。对于紧凑字符串,它们仅在需要 UTF-8 缓冲区(例如PyUnicode_AsUTF8AndSize)或Py_UNICODE缓冲区(例如已弃用PyUnicode_AS_UNICODE)的函数被调用时才分配。
对于使用已弃用Py_UNICODE的 API创建的字符串,wstr指针有额外的用途。它指向字符串数据的唯一版本,直到在字符串PyUnicode_READY上调用宏。第一次准备好字符串时,data将创建一个新缓冲区,并将字符存储在其中,使用可能是 Latin-1、UTF-16 和 UTF-32 中最紧凑的编码。该wstr缓冲区将被保留,因为它可能被希望查找一个其他过时的API函数,以后需要PY_UNICODE的字符串。
有趣的是,您现在正在询问 CPython 的内部字符串表示,因为目前正在讨论是否wchar *可以在即将发布的 Python 版本中删除已弃用的字符串 API 函数和实现细节(如指针)。Python 3.11.0(预计将于 2022 年发布)看起来可能会发生这种情况,尽管在那之前计划仍可能发生变化,特别是如果对在野外使用的代码的影响比预期的更严重的话。