如何将代理项对转换为 Python 中的普通字符串?

How can I convert surrogate pairs to normal string in Python?

这是 Converting to Emoji. In that question, the OP had a json.dumps()-encoded file with an emoji represented as a surrogate pair - \ud83d\ude4f. S/he was having problems reading the file and translating the emoji correctly, and the correct answer 的后续行动 json.loads() 文件中的每一行,json 模块将处理从代理项对返回到 (I' m 假设 UTF8 编码)表情符号。

这是我的情况:假设我只有一个常规的 Python 3 unicode 字符串,其中包含一个代理项对:

emoji = "This is \ud83d\ude4f, an emoji."

如何处理此字符串以从中获取 emoji 的表示形式?我正在寻找这样的东西:

"This is , an emoji."
# or
"This is \U0001f64f, an emoji."

我试过:

print(emoji)
print(emoji.encode("utf-8")) # also tried "ascii", "utf-16", and "utf-16-le"
json.loads(emoji) # and `.encode()` with various codecs

通常我会收到类似于 UnicodeEncodeError: XXX codec can't encode character '\ud83d' in position 8: surrogates no allowed 的错误。

我在 Linux 运行 宁 Python 3.5.1,$LANG 设置为 en_US.UTF-8。我在命令行的 Python 解释器和 Sublime Text 的 IPython 运行ning 中都有 运行 这些示例 - 似乎没有任何区别.

您在磁盘上的 json 文件中混合了文字字符串 \ud83d(六个字符:\ u d 8 3 d)和 单个 内存中的字符 u'\ud83d'(在 Python 源代码中使用字符串文字指定)。这是len(r'\ud83d') == 6len('\ud83d') == 1在Python上的区别 3.

如果您看到 '\ud83d\ude4f' Python 字符串(2 个字符),那么上游存在错误。通常,你不应该得到这样的字符串。如果你得到了一个,但你无法修复生成它的上游;您可以使用 surrogatepass 错误处理程序修复它:

>>> "\ud83d\ude4f".encode('utf-16', 'surrogatepass').decode('utf-16')
''

Python 2 was more permissive.

注意:即使您的 json 文件包含文字 \ud83d\ude4f(12 个字符);你不应该得到代理对:

>>> print(ascii(json.loads(r'"\ud83d\ude4f"')))
'\U0001f64f'

注意:结果是1个字符('\U0001f64f'),不是代理对('\ud83d\ude4f')。

因为这是一个重复出现的问题,错误信息有点模糊,这里有一个更详细的解释。

代理项是一种表达大于 U+FFFF 的 Unicode 代码点的方法。

回想一下,Unicode 最初指定包含 65,536 个字符,但很快发现这不足以容纳世界上所有的字形。

作为(否则固定宽度)的扩展机制UTF-16 encoding, a reserved area was set up to contain a mechanism for expressing code points outside the Basic Multilingual Plane:这个特殊区域中的任何代码点都必须跟在同一区域的另一个字符代码之后,它们一起表示数字大于旧限制的代码点。

(严格来说,surrogates 区域分为两半;一对中的第一个surrogates 需要来自High Surrogates 的一半,第二个来自Low Surrogates。令人困惑的是,High Surrogates U+D800 -U+DBFF 的代码点编号低于低代理项 U+DC00-U+DFFF。)

这是专门支持UTF-16编码的遗留机制,不应在其他编码中使用;他们不需要它,适用的标准明确指出这是不允许的。

换句话说,虽然U+12345可以用代理对U+D808 U+DF45来表达,但除非你专门使用UTF-16,否则你应该直接表达它。

更详细地说,这是在 UTF-8 中将其表示为单个字符的方式:

0xF0 0x92 0x8D 0x85

这是对应的代理序列:

0xED 0xA0 0x88
0xED 0xBD 0x85

正如接受的答案中已经建议的那样,您可以使用

之类的东西往返
>>> "\ud808\udf45".encode('utf-16', 'surrogatepass').decode('utf-16').encode('utf-8')
b'\xf0\x92\x8d\x85'

也许还可以看到 http://www.russellcottrell.com/greek/utilities/surrogatepaircalculator.htm