【发布时间】:2016-03-19 07:15:46
【问题描述】:
因此,Python 3.3 引入了 PEP 393,它改变了 Python str 对象(Unicode 对象)的实现,因此在内部,它们以内存高效的方式表示,同时仍然允许随机访问每个字符。这是通过扫描字符串以查找最大的 Unicode 代码点,然后根据最大代码点的大小选择 Unicode 编码来完成的。这样 ASCII 字符串每个字符只需要 1 个字节,但带有东亚(中文/日文/韩文)代码点的字符串每个字符需要 4 个字节。这在内存方面比早期的实现更有效,无论字符串中的实际代码点如何,每个字符总是使用 2 或 4 个字节。
所以我理解 PEP 393 的目的,但我真的很困惑我应该如何从 UTF-8 编码的 C 字符串创建 Python Unicode 对象。从 UTF-8 C 字符串创建 Python str 是一个非常常见的要求。事实上,旧的(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 似乎只是表示输入缓冲区每个 character 有 1 个字节 - 这与 UTF-not 相同8,它是可变长度的,每个字符可以有 1 到 4 个字节。
那么,我们如何使用新的 PEP 393 API 从 UTF-8 C 字符串创建 Python Unicode 对象?
在阅读the docs 和 PEP 393 本身之后,在我看来,这样做的唯一方法是手动计算自己的最大字符,然后调用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 对象?或者,如果我们希望避免使用已弃用的功能,是否真的有必要手动完成所有这些操作?
【问题讨论】:
-
不推荐使用
u = NULL的PyUnicode_FromStringAndSize用法吗?
标签: python c unicode python-c-api python-internals