Unicode表示形式转换为格式化Unicode?

3
我有些困难理解Unicode表达式转换为相应字符的过程。我已经查看了Unicode规范,并找到了各种格式为U+1F600的字符串。据我所见,似乎没有内置函数能够将这些字符串转换为正确的Python格式,如\U0001F600
在我的程序中,我编写了一个小型正则表达式来查找这些U\+.{5}模式,并用\U000替换U+。然而,我发现并非所有Unicode字符的语法都相同,例如零宽连接符实际上应该从U+200D转换为\u200D
由于我不知道每个正确Unicode逃逸序列的变化,处理这种情况的最佳方法是什么?是只检查有限数量的这些特殊字符,还是我完全走错了路?
Python版本是2.7。

抱歉,我会添加进去的。它是Python 2.7版本。 :) - lindsay
u'\u200D' == u'\U0000200D' - ThisSuitIsBlackNot
谢谢!我想知道我添加的示例是否会接受额外的零。如果是这样,也许可以解决不均匀的错误。结果证明确实如此,因为\U00001F600是一个不同的字符。无论如何还是感谢您! - lindsay
3个回答

3

我认为最可靠的方法是将数字解析为整数,然后使用unichr查找该代码点:

unichr(0x1f600)  # or: unichr(int('1f600', 16))

注意:在Python 3中,只需使用chr函数。


感谢这些答案。也许是我的问题,但我刚刚尝试了Python 2.7的示例代码,它抛出了这个错误:unichr() arg not in range(0x10000) (narrow Python build)。有什么想法吗? - lindsay
@lindsay 这是 Python 2.7 窄版构建形式的限制(我相信这是默认设置) - 它无法将那些更高数字字符表示为单个 Unicode 代码点,并需要使用代理对。在某些 Python 3 版本中,他们解决了这个问题,目前无法确切记住是哪个版本。 - Mark Ransom
3
没问题,那只是一个糟糕的 Python 版本。你可以尝试使用 struct.pack('i', 0x1f600).decode('utf-32'),看看输出结果如何? - wim
1
使用struct.pack既聪明又令人讨厌。你应该将它编辑到你的答案中。@lindsay没有理由unichr不能做同样的事情,只是不够好。 - Mark Ransom
我会在评论中留下这个“令人讨厌”的hack。换言之,获取一个更好的Python构建版本 :) - wim
显示剩余4条评论

3

U+NNNN只是用于讨论Unicode的常见表示法。Python中单个Unicode字符的语法有以下几种:

  • u'\xNN',适用于U+00FF及以下的Unicode字符
  • u'\uNNNN',适用于U+FFFF及以下的Unicode字符
  • u'\U00NNNNNN',适用于U+10FFFF(最大值)及以下的Unicode字符

注意:N是一个十六进制数字。

在输入字符时,请使用正确的表示法。即使是低字符,您也可以使用更长的表示法:

u'A' == u'\x41' == u'\u0041' == u'\U00000041'

程序化地,您也可以使用unichr(n) (Python 2) 或者 chr(n) (Python 3) 来生成正确的字符。
需要注意的是,在Python 3.3之前,有窄版和宽版Unicode编译版本。 unichr/chr 只能支持 sys.maxunicode,在窄版中为65535(0xFFFF),在宽版中为1114111(0x10FFFF)。Python 3.3统一了这些编译版本,并解决了许多关于Unicode的问题。
如果您正在处理以格式U+NNNN表示的文本字符串, 这里是一个正则表达式(Python 3), 它查找U+和4-6个十六进制数字,并用chr()版本替换它们。请注意,ASCII字符(Python 2)或可打印字符(Python 3)将显示实际字符而不是转义版本。
>>> re.sub(r'U\+([0-9A-Fa-f]{4,6})',lambda m: chr(int(m.group(1),16)),'testing U+1F600')
'testing \U0001f600'
>>> re.sub(r'U\+([0-9A-Fa-f]{4,6})',lambda m: chr(int(m.group(1),16)),'testing U+5000')
'testing \u5000'
>>> re.sub(r'U\+([0-9A-Fa-f]{4,6})',lambda m: chr(int(m.group(1),16)),'testing U+0041')
'testing A'
>>> re.sub(r'U\+([0-9A-Fa-f]{4,6})',lambda m: chr(int(m.group(1),16)),'testing U+0081')
'testing \x81'

O.P.正在处理像"U+1F600"这样的文本。由于它不是Python语法,所以需要进行一些解析或转换,对吧? - wim
是的,正如@wim所说,我正在编辑Unicode字符的文本表达式。无论如何,感谢您提供详细的答案! :) - lindsay
1
@lindsay 添加了一个正则表达式以解析该语法,但请注意如果您使用窄版的 Python 2 ,它将无法处理 U+10000 - U+10FFFF 的编码。 - Mark Tolonen
3
在 Python 3.3 以前,Unicode 字符在窄版中使用 UTF-16LE 编码进行内部存储,而在宽版中则使用 UTF-32LE。UTF-16 编码对于 Unicode 编码点 U+10000 以下使用一个 16 位单词,对于其余所有字符则使用两个 16 位单词。因此存在奇怪的情况,例如 len(u'\U0001F600') == 2len(u'\U0000FFFF') == 1。在窄版中,unichr() 仅支持返回 U+10000 以下的编码点。@wim 的 struct.pack 技巧解决了这个问题。 - Mark Tolonen
@MarkTolonen非常感谢您深入的回答!这个Unicode的东西很棘手,但是这有助于我更好地掌握它。 :) 这解释了为什么我一直在尝试对这些字符串进行编码和解码时卡住了。 - lindsay
显示剩余2条评论

0
你可以查看 json 模块的实现。看起来并不是那么简单:
# Unicode escape sequence
uni = _decode_uXXXX(s, end)
end += 5
# Check for surrogate pair on UCS-4 systems
if sys.maxunicode > 65535 and \
0xd800 <= uni <= 0xdbff and s[end:end + 2] == '\\u':
    uni2 = _decode_uXXXX(s, end + 1)
    if 0xdc00 <= uni2 <= 0xdfff:
        uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
        end += 6
char = unichr(uni)

(来自cpython-2.7.9 / Lib / json / decoder.py第129-138行)

我认为直接使用json.loads会更容易:

>>> print json.loads('"\\u0123"')
ģ

如果是JSON格式,当然可以。但是谁说一定是JSON格式呢? - wim
@wim,您可以从Unicode序列创建字符串并将其提供给JSON解析器。 - myaut

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