在Python中转义Unicode字符串

6
在Python中,以下三个命令会打印相同的表情符号:
print "\xF0\x9F\x8C\x80"

print u"\U0001F300"

print u"\ud83c\udf00"

我该如何在\x、\u和\U之间进行转换?我无法弄清这些十六进制数是如何相等的。


1
我的意思是,对我来说不明显 \xF0\x9F 和 0001 和 d83c 是同一个数字? - Jose G
1
它们不是相同的数字!它们是相同代码点的不同编码。 - wim
2
u"\U0001F300"==u"\ud83c\udf00"是Python 2 'narrow' builds的缺陷,通常不是您想要依赖的内容。如果您想使用表情符号,请始终使用\U形式。 - bobince
我认为\xF0\x9F\x8C\x80等同于ð。你为什么在代码中写了``呢? - Toothpick Anemone
@SamuelMuldoon 因为那是Python 2.7的代码。Python 3的等效代码应该是 print(b"\xF0\x9F\x8C\x80".decode()) - wim
4个回答

11
第一个是字节串:
>>> "\xF0\x9F\x8C\x80".decode('utf8')
u'\U0001f300'

u"\ud83c\udf00" 是 UTF16 版本(四位unicode转义)。

u"\U0001F300" 是代码点的实际索引。


但是这些数字有什么关系呢? 这是个困难的问题。它由编码定义,没有明显的关系。为了给您一个概念,下面是“手动”将索引为0x1F300的代码点编码成UTF-8的示例:

旋风字符的索引为0x1f300,属于范围0x00010000 - 0x001FFFFF。该范围的模板如下:

11110... 10...... 10...... 10......

你需要用二进制表示填写点的位置。我无法告诉你为什么模板看起来像这样,这只是utf-8的定义。

这里是我们代码点的二进制表示:

>>> u''
u'\U0001f300'
>>> unichr(0x1f300)
u'\U0001f300'
>>> bin(0x1f300)
'0b11111001100000000'

因此,如果我们按以下方式填充字符串模板(由于模板中的插槽比我们的数字中的有效数字多一些,因此需要添加一些前导零),我们将得到以下结果:

11110... 10...... 10...... 10......
11110000 10011111 10001100 10000000

现在让我们把它转换回十六进制。
>>> 0b11110000100111111000110010000000
4036988032
>>> hex(4036988032)
'0xf09f8c80'

这里是代码点的UTF8表示方式。

对于UTF16,您的代码点有一个类似的“神奇配方”:从索引中减去0x10000,然后用零填充以获得20位二进制表示。前十位加上0xD800为第一个16位码元,后十位加上0xDC00为第二个16位码元。

>>> bin(0x1f300 - 0x10000)[2:].rjust(20, '0')
'00001111001100000000'
>>> _[:10], _[10:]
('0000111100', '1100000000')
>>> hex(0b0000111100 + 0xd800)
'0xd83c'
>>> hex(0b1100000000 + 0xdc00)
'0xdf00'

这里是UTF-16版本,即带有小写\\u转义的版本。
你可能已经明白,这些表示中的十六进制数字之间可能没有明显的数值关系,它们只是同一代码点的不同编码。

2
四个1表示编码长度为4个字节。每个连续字节都以10开头。 - Ignacio Vazquez-Abrams
啊,是的,因为utf8是可变宽度编码。谢谢@IgnacioVazquez-Abrams :) - wim
我有一个字符串,看起来像 OP 给出的第二个例子:b'[11/5/19, 11:38:00 AM] \xe2\x80\xaa+49\xc2\xa0178\xc2\xa03464334\xe2\x80\xac: \xe2\x80\x8eMessages and calls are end-to-end encrypted. No one outside of this chat, not even WhatsApp, can read or listen to them',我想要“转义”Unicode字符,但是当我使用.decode('utf-8')时,结果的字节字符串仍然只有Unicode代码点,而不是实际将它们转换为可打印字符:'[11/5/19, 11:38:00 AM] \u202a+49\xa0178\xa03464334\u202c: \u200eMessages (截断)。 (1/2) - Raleigh L.
我想让实际的Unicode字符表示显示出来,而不是Unicode代码点(例如A0178)。 - Raleigh L.

3

你的第一个字符串是一个字节字符串。它打印单个表情符号的事实意味着你的控制台被配置为打印UTF-8编码字符。

你的第二个字符串是一个Unicode字符串,只有一个代码点U+1F300\U指定下一个8个十六进制数字应该被解释为一个代码点。

第三个字符串利用了Python 2中存储Unicode字符串的一种怪癖。你给出了两个UTF-16实体,它们一起形成了与前一个字符串相同的单个代码点U+1F300。每个\u后面跟着4个十六进制数字。单独这些字符不是有效的Unicode,但因为Python 2在内部将其Unicode存储为UTF-16,所以可以正常工作。在Python 3中,这将无效。

当您打印Unicode字符串时,如果您的控制台编码已知为UTF-8,则Unicode字符串会被编码为UTF-8字节。因此,这3个字符串最终在输出时生成相同的字节序列,生成相同的字符。

2
请参阅Python源代码中的Unicode文字 (Unicode Literals in Python Source Code)
在Python源代码中,Unicode文字用以字符串形式表示,前缀为‘u’或‘U’: u'abcdefghijk'。可以使用\u转义序列写入特定的代码点,随后是四个十六进制数字,给出代码点。\U转义序列类似,但需要8个十六进制数字,而不是4个
In [1]: "\xF0\x9F\x8C\x80".decode('utf-8')
Out[1]: u'\U0001f300'

In [2]: u'\U0001F300'.encode('utf-8')
Out[2]: '\xf0\x9f\x8c\x80'

In [3]: u'\ud83c\udf00'.encode('utf-8')
Out[3]: '\xf0\x9f\x8c\x80'

\uhhhh     --> Unicode character with 16-bit hex value  
\Uhhhhhhhh --> Unicode character with 32-bit hex value

在Unicode转义中,第一种形式使用四个十六进制数字来编码2字节(16位)字符编码点,而第二种形式使用八个十六进制数字来编码4字节(32位)编码点。字节字符串只支持用于编码文本和其他基于字节的数据的十六进制转义。

1
其他答案描述了Unicode字符如何在Python 2.x中被编码或嵌入为文字。让我回答你更元的问题,“对我来说不太明显\xF0\x9F和0001和d83c是同一个数字?”
赋予每个Unicode“代码点”,大致上可以赋予每个“字符”,可以用多种方式进行编码。这类似于整数可以用几种方式进行编码的方式:
  • 0b1100100(二进制,基数为2)
  • 0144(八进制,基数为8)
  • 100(十进制,基数为10)
  • 0x64(十六进制,基数为16)

那些都是相同的值,即十进制100,具有不同的编码方式。以下是Python中的真实表达式:
0b1100100 == 0144 == 100 == 0x64

Unicode编码略微复杂,但原理相同。仅仅因为值看起来不同,并不意味着它们代表的值不同。在Python 2中:

u'\ud83c\udf00' == u'\U0001F300' == "\xF0\x9F\x8C\x80".decode("utf-8")

Python 3改变了字符串字面值的规则,但仍然是真实的:
u'\U0001F300' == b"\xF0\x9F\x8C\x80".decode("utf-8") 

当需要显式的 b(字节前缀)时,必须使用。 u(Unicode前缀)是可选的,因为所有字符串都被认为包含Unicode,并且只有在3.3及更高版本中才允许使用u。多字节组合字符……呃,它们不是很好看,对吧?

因此,您展示了Unicode CYCLONE代码点的各种编码方式,其他答案展示了一些在代码点之间移动的方法。请参见此处以获取该字符的更多编码。


在我的Python(v2.7.8)中,u'\ud83c\udf00' == u'\U0001F300'并不是真的。 - wim
@wim 是什么平台?我刚在 Mac 上,Python 2.7.9 下重新验证了一下(http://screencast.com/t/foYwdr06j36)。这在 Python 3 上不是真的,但在这里的 Python 2.5-2.7.9 上是真的。 - Jonathan Eunice
在Linux上(Ubuntu 14.10),这里 - wim
1
顺便说一句,在这种情况下,我不同意你的观点,“Python 3更复杂”。实际上,它比Python 2更简单。Python 2存在与代理对相关的奇怪边缘情况(请参见Mark Ransom的答案)。 - wim
@wim 嗯,Python 3 不自动合并 u'\ud83c\udf00' UTF-16 对,而 Python 2 和其他语言如 Java 和 C# 则会。Python 3 让某些事情变得更简单。通常这更好,因为这些事情是人们需要对 Unicode 完成的主流事项。但它会使其他事情更难,通过分离字符串和字节来实现。 - Jonathan Eunice

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接