Python 3.3 C-API 和 UTF-8 字符串

Python 3.3 C-API and UTF-8 Strings

所以,Python3.3引入了PEP 393,改变了Pythonstr对象(Unicode对象)的实现因此在内部,它们以一种内存高效的方式表示,同时仍然允许随机访问每个字符。这是通过扫描最大 Unicode 代码点的字符串,然后根据最大代码点的大小选择 Unicode 编码来完成的。这样 ASCII 字符串每个字符只需要 1 个字节,但是带有东亚 (Chinese/Japanese/Korean) 代码点的字符串每个字符需要 4 个字节。这在内存方面比早期的实现更有效,无论字符串中的实际代码点如何,每个字符总是使用 2 或 4 个字节。

所以我理解 PEP 393 的目的,但我真的很困惑我应该如何从UTF-8 编码的 C 字符串。从 UTF-8 C 字符串创建 Python str 极其常见的 要求。事实上,旧的(pre Python 3.3)C-API 有一个完全用于该目的的函数:

PyObject* PyUnicode_FromStringAndSize(const char *u, Py_ssize_t size)

此函数采用 UTF-8 C 字符串和大小参数,并从该字符串创建 Python Unicode 对象。

但是,这个函数现在 在 PEP 393 之后被弃用。但是我仍然不确定应该用什么来代替它。

起初我以为我们可以使用新的PyUnicode_FromKindAndData函数:

PyObject* PyUnicode_FromKindAndData(int kind, const void *buffer, Py_ssize_t size)

这个新函数接受一个 "kind" 参数,它指示输入缓冲区是 UCS1、UCS2 还是 UCS4。基于该参数,它创建了一个新的紧凑 Python Unicode 对象。所以,一开始我以为说PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, buf, size)基本上就等同于旧的PyUnicode_FromStringAndSize。我在想 PyUnicode_1BYTE_KIND 意味着 Python 应该假定输入缓冲区是 UTF-8 字符串。但事实似乎并非如此。 UCS1 与 UTF-8 不同,PyUnicode_1BYTE_KIND 似乎只是表示输入缓冲区每个 字符 有 1 个字节 - 这是 not 与 UTF-8 相同,它是可变长度的,每个字符可以有 1 到 4 个字节。

那么,我们如何使用新的 PEP 393 API 从 UTF-8 C 字符串创建一个 Python Unicode 对象?

看完the docs and PEP 393 itself, it seems to me that the only way to do this is to manually compute the maximum character yourself, and then call PyUnicode_New。然后迭代新创建的字符串缓冲区,手动将UTF-8 C字符串中的每个代码点转换为基于maxchar的正确编码,使用PyUnicode_WRITE复制每个字符在循环中。


...除了我有点惊讶 API 实际上需要所有这些手动工作 - 包括 从 UTF-8 到 UTF-32 或 UTF 的所有转换-16,考虑到诸如代理对之类的东西。基本上手动完成所有这些转换是 很多 的努力,令我惊讶的是 Python C-API 没有公开函数来执行此操作更简单的方法。我的意思是,显然这样的代码存在于 Python 源代码中,因为旧的已弃用 PyUnicode_FromStringAndSize 正在做 正是 。它正在将 UTF-8 转换为 UTF-16 或 UTF-32(取决于平台)。但现在有了 PEP 393,似乎所有这些都必须手动完成。

我是不是漏掉了什么?有没有更简单的方法来使用 UTF-8 C 字符串作为输入来创建 Python Unicode 对象?或者,如果我们希望避免使用已弃用的功能,是否真的有必要手动完成所有这些操作?

PEP 393 没有指定任何新的 API 用于从 UTF-* 编码转换为 Unicode。旧的 API 仍然适用。

如果不需要错误处理,这2个还是可以用的

PyObject* PyUnicode_FromStringAndSize(const char *u, Py_ssize_t size)
PyObject* PyUnicode_FromString(const char*u)

只是,u第一个不能为NULL(已弃用)。

如果您需要错误处理或代理转义,请使用 PyUnicode_DecodeUTF8:

PyObject* PyUnicode_DecodeUTF8(const char *s, Py_ssize_t size, const char *errors)

这些都在内部使用了PyUnicode_DecodeUTF8StatefulPyUnicode_DecodeUTF8Stateful now creates new canonical PyUnicodeObjects.


至于将 PyUnicodeObject 的 UTF-8 表示形式获取为 char *,请使用

char* PyUnicode_AsUTF8AndSize(PyObject *unicode, Py_ssize_t *size)
char* PyUnicode_AsUTF8(PyObject *unicode)

此表示缓存在 PyUnicodeObject 对象本身中,并且只要该对象本身还存在就有效。如果所有字符都是 ASCII,这些形式特别有用,那么返回的 UTF-8 指针可以指向现有字符。