如何将诸如'U+1F600'
这样的形式化Unicode表示法转换为'\U0001F600'
这样的形式,我在在线网站上看到它表示为“ Python Src”?
我的最终目标是将Unicode用于Python(2.x)中的表情符号,并且我可以通过以下方式实现它:
unicode_string = '\U0001F600'
unicode_string.decode('unicode-escape')
如果您能提及上述问题涉及的不同字符集,我将不胜感激。
答案 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(请注意,我什至没有真正 处理它正确……)是一个巨大的痛苦。