【问题标题】:Python 3.3 C-API and UTF-8 StringsPython 3.3 C-API 和 UTF-8 字符串
【发布时间】: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 = NULLPyUnicode_FromStringAndSize 用法吗?

标签: python c unicode python-c-api python-internals


【解决方案1】:

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

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

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 现在创建 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 指针可以只指向现有字符。

【讨论】:

  • PyUnicode_FromStringAndSize 和旧的 UTF-* API 函数现在是否创建新的“规范”unicode 对象 - 即这些函数现在是否会自动扫描输入的 UTF-8 字符串以找到最大的代码点,以及然后用它来确定所需的最小字符大小?
猜你喜欢
  • 1970-01-01
  • 2017-03-04
  • 2015-06-02
  • 2023-03-27
  • 2018-01-26
  • 2017-05-23
  • 1970-01-01
  • 1970-01-01
  • 2022-11-12
相关资源
最近更新 更多