如何在Python中解释Unicode表示法?

时间:2018-07-29 23:11:20

标签: python-2.7 unicode

如何将诸如'U+1F600'这样的形式化Unicode表示法转换为'\U0001F600'这样的形式,我在在线网站上看到它表示为“ Python Src”?

我的最终目标是将Unicode用于Python(2.x)中的表情符号,并且我可以通过以下方式实现它:

unicode_string = '\U0001F600'
unicode_string.decode('unicode-escape')

如果您能提及上述问题涉及的不同字符集,我将不胜感激。

1 个答案:

答案 0 :(得分:4)

最简单的方法是将符号视为字符串:

>>> s = 'U+1F600'
>>> s[2:] # chop off the U+
'1F600'
>>> s[2:].rjust(8, '0') # pad it to 8 characters with 0s
'0001F600'
>>> r'\U' + s[2:].rjust(8, '0') # prepend the `\U`
'\\U0001F600'

将字符串解析为十六进制然后格式化返回的数字可能会更干净一些:

>>> int(s[2:], 16)
128512
>>> n = int(s[2:], 16)
>>> rf'\U{n:08X}'
'\\U0001F600'

...但是我不确定以这种方式理解真的更容易。


如果您需要从较大的字符串中提取这些内容,则可能需要一个正则表达式。

我们要匹配文字U+,后跟1到8个十六进制数字,对吗?因此,这就是U\+[0-9a-fA-F]{1,8}。除非我们真的不需要包含U+只是为了将其与[2:]一起使用,所以让我们将其余的分组:U\+([0-9a-fA-F]{1,8})

>>> s = 'Hello U+1F600 world'
>>> re.search(r'U\+([0-9a-fA-F]{1,8})', s)
<_sre.SRE_Match object; span=(6, 13), match='U+1F600'>
>>> re.search(r'U\+([0-9a-fA-F]{1,8})', s).group(1)
'1F600'

现在,我们可以将re.sub与一个函数一起应用\U前置和rjust填充:

>>> re.sub(r'U\+([0-9a-fA-F]{1,8})', lambda match: r'\U' + match.group(1).rjust(8, '0'), s)
'Hello \\U0001F600 world'

如果您离线定义函数,则可能更容易理解:

>>> def padunimatch(match):
...     return r'\U' + match.group(1).rjust(8, '0')
>>> re.sub(r'U\+([0-9a-fA-F]{1,8})', padunimatch, s)
'Hello \\U0001F600 world'

或者,如果您希望以数字方式进行操作:

>>> def padunimatch(match):
...     n = int(match.group(1), 16)
...     return rf'\U{n:08X}'
>>> re.sub(r'U\+([0-9a-fA-F]{1,8})', padunimatch, s)
'Hello \\U0001F600 world'

当然,您已经知道如何做最后一部分,因为这是您的问题,对吗?嗯,还不完全:您不能仅在decode上调用字符串bytes。解决此问题的最简单方法是直接使用编解码器:

>>> x = 'Hello \\U0001F600 world'
>>> codecs.decode(x, 'unicode_escape')
'Hello  world'

…除非您正在使用Python2。否则,str类型不是Unicode字符串,而是字节字符串,因此decode实际上可以正常工作。但是在Python 2中,除非所有文本都是纯ASCII(任何非ASCII字符都编码为U+xxxx序列),否则您将遇到其他问题。

例如,假设您输入的是:

>>> s = 'Hej U+1F600 världen'

在Python 3中,这很好。 s是Unicode字符串。在幕后,我的控制台正在将Python UTF-8编码的字节发送到标准输入,并期望从标准输出中获取UTF-8编码的字节,但这就像魔术一样。 (嗯,不是很神奇,您可以print(sys.stdin.encoding, sys.stdout.encoding)看到Python知道我的控制台是UTF-8,并使用它来代表我进行解码和编码。)

在Python 2中不是。如果我的控制台是UTF-8,我实际上所做的等同于:

>>> s = 'Hej U+1F600 v\xc3\xa4rlden'

…,如果我尝试将其解码为unicode-escape,Python 2会将这些\xc3\xa4字节视为Latin-1字节,而不是UTF-8:

>>> s = 'Hej \U0001F600 v\xc3\xa4rlden'

...所以最终得到的是:

>>> s.decode('unicode_escape')
u'Hej \U0001f600 v\xc3\xa4rlden'
>>> print(s.decode('unicode_escape'))
Hej  världen

但是,如果您先尝试将其解码为UTF-8,然后再将那个解码为unicode_escape,该怎么办?

>>> s.decode('utf-8')
u'Hej \\U0001F600 v\xe4rlden'
>>> print(s.decode('utf-8'))
Hej \U0001F600 världen
>>> s.decode('utf-8').decode('unicode-escape')
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 16: ordinal not in range(128)

与Python 3不同,Python 3不会让您在Unicode字符串上调用decode,而Python 2则允许您这样做-但是它通过尝试先将encode转换为ASCII来处理它,因此它有decode的东西,显然在这里失败了。

您不能像在Python 3中那样直接使用编解码器:

>>> codecs.decode(s.decode('utf-8'), 'unicode_escape')
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 16: ordinal not in range(128)

您可以对UTF-8进行解码,然后对结果进行unicode转义,然后对所有内容进行ununicode转义,但这还不太正确:

>>> print(s.decode('utf-8').encode('unicode_escape').decode('unicode_escape'))
Hej \U0001F600 världen

为什么?因为unicode-escape在修复我们现有的Unicode字符的同时,也逃脱了我们的反斜杠!

如果您知道在您不想解析的原始源中绝对没有\U转义符,则有一个快速的解决方法:仅replace转义的反斜杠:

>>> print(s.decode('utf-8').encode('unicode_escape').replace(r'\\U', r'\U').decode('unicode_escape'))
Hej  världen

如果这一切看起来都很痛苦……是的,这就是Python 3存在的原因,因为在Python 2中正确处理了Unicode(请注意,我什至没有真正 处理它正确……)是一个巨大的痛苦。