Python >= 3.3 内部字符串表示

Python >= 3.3 Internal String Representation

我正在研究 Python 在 PEP 393 之后如何表示字符串,但我不理解 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;

如果我错了请纠正我,但我的理解是 PyASCIIObject 仅用于具有 ASCII 字符的字符串,PyCompactUnicodeObject 使用 PyASCIIObject 结构并且它用于具有至少一个非 ASCII 字符的字符串,而 PyUnicodeObject 是用于遗留功能。对吗?

此外,为什么 PyASCIIObject 使用 wchar_t?一个 char 不足以表示 ASCII 字符串吗? 另外,如果 PyASCIIObject 已经有一个 wchar_t 指针,为什么 PyCompactUnicodeObject 也有一个 char 指针?我的理解是两个指针都指向同一个位置,但为什么要同时包含两个指针?

PEP 373 is really the best reference for your questions, though the C-API docs 有时也需要。让我们一一解答您的问题:

  1. 你选对了类型。但是有一个 non-obvious 问题:当您使用任何一种 "compact" 类型(PyASCIIObjectPyCompactUnicodeObject)时,结构本身只是一个 header.字符串的实际数据存储在内存中的结构之后。数据使用的编码由 kind 字段描述,并将取决于字符串中的最大字符值。

  2. 前两个结构中的wstrutf8指针是可以存储转换后表示的地方,如果C代码需要的话。对于 ASCII 字符串(使用 PyASCIIObject),UTF-8 数据不需要缓存指针,因为 ASCII 数据本身是 UTF-8 兼容的。宽字符缓存仅由已弃用的函数使用。

    这两个缓存指针永远不会指向同一个地方,因为它们的类型不直接兼容。对于紧凑字符串,它们仅在调用需要 UTF-8 缓冲区(例如 PyUnicode_AsUTF8AndSize) or a Py_UNICODE buffer (e.g. the deprecated PyUnicode_AS_UNICODE)的函数时分配。

    对于使用已弃用的基于 Py_UNICODE 的 API 创建的字符串,wstr 指针有额外的用途。它指向字符串数据的 only 版本,直到对字符串调用 PyUnicode_READY 宏。第一次准备好字符串时,将创建一个新的 data 缓冲区,并使用 Latin-1、UTF-16 和 UTF-32 中最紧凑的编码将字符存储在其中。 wstr 缓冲区将被保留,因为稍后其他想要查找 PY_UNICODE 字符串的已弃用 API 函数可能需要它。

有趣的是,您现在正在询问 CPython 的内部字符串表示,因为有一个讨论 currently ongoing 关于是否已弃用的字符串 API 函数和实现细节例如 wchar * 指针可以在即将推出的 Python 版本中删除。看起来 Python 3.11.0(预计将于 2022 年发布)可能会发生这种情况,但在此之前计划仍可能会发生变化,特别是如果对在野使用的代码的影响比预期的更严重.