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

问题描述 投票:0回答:2

这是如何将包含 Unicode 代理项对的 JSON 编码数据转换为字符串? 的后续内容。在这个问题中,OP 有一个

json.dumps()
编码文件,其中的表情符号表示为代理对 -
\ud83d\ude4f
。他们在正确读取文件和翻译表情符号时遇到问题,正确的 answer 是文件中的每一行
json.loads()
,并且
json
模块将处理从代理对转换回(我假设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 中运行了这些示例 - 似乎没有任何差异。

python python-3.x unicode surrogate-pairs
2个回答
67
投票

您在磁盘上的 json 文件中混合了文字字符串

\ud83d
(六个字符:
\ u d 8 3 d
)和内存中的单个字符
u'\ud83d'
(在 Python 源代码中使用字符串文字指定)。这是 Python 3 上
len(r'\ud83d') == 6
len('\ud83d') == 1
之间的区别。

如果您看到

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

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

Python 2 更加宽松

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

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

注意:结果是 1 字符 (

'\U0001f64f'
),而不是代理对 (
'\ud83d\ude4f'
)。


21
投票

因为这是一个反复出现的问题,并且错误消息有点晦涩,这里有更详细的解释。

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

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

作为(固定宽度)UTF-16编码的扩展机制,设置了一个保留区域来包含在基本多语言平面之外表达代码点的机制:此特殊区域中的任何代码点都会后面必须跟来自同一区域的另一个字符代码,它们一起表示一个数字大于旧限制的代码点。

(严格来说,代理区分为两半;一对中的第一个代理需要来自高代理区,第二个来自低代理区。令人困惑的是,高代理区 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

Python 当然可以原生表达任何 Unicode 代码点,无论是作为文字字符还是使用转义序列(如

\U00012345
)。请注意,对于
\u
之前的代码点有一个单独的小写字母
\uffff
,它只允许在
\u
之后有四个十六进制数字。一些符号,特别是 JSON,缺少较长形式的
\U00011345
转义代码,因此您实际上被迫对 BMP 之外的代码点(即 U+FFFF 之上)使用代理序列。

© www.soinside.com 2019 - 2024. All rights reserved.