如何在Python 3中遍历Unicode字符?
我需要逐个字符地处理一个Python字符串,但简单的“for”循环却给了我UTF-16编码单元:
str = "abc\u20ac\U00010302\U0010fffd"
for ch in str:
code = ord(ch)
print("U+{:04X}".format(code))
这段代码输出:
U+0061
U+0062
U+0063
U+20AC
U+D800
U+DF02
U+DBFF
U+DFFD
而我想要的是:
U+0061
U+0062
U+0063
U+20AC
U+10302
U+10FFFD
有没有办法让Python给我Unicode编码点的序列,不管这个字符串在底层是怎么编码的?我在Windows上测试,但我需要的代码能在任何地方都能用。只要能在Python 3上运行就行,我不关心Python 2.x。
到目前为止,我能想到的最好办法是这个:
import codecs
str = "abc\u20ac\U00010302\U0010fffd"
bytestr, _ = codecs.getencoder("utf_32_be")(str)
for i in range(0, len(bytestr), 4):
code = 0
for b in bytestr[i:i + 4]:
code = (code << 8) + b
print("U+{:04X}".format(code))
但我希望能有更简单的方法。
(对于Unicode术语的严格挑剔会被毫不留情地打击。我的意思已经很清楚了,请不要浪费空间讨论“但UTF-16技术上也是Unicode”这种争论。)
3 个回答
如果你把字符串创建成unicode对象,它应该能自动一个一个字符地分开。比如:
Python 2.6:
s = u"abc\u20ac\U00010302\U0010fffd" # note u in front!
for c in s:
print "U+%04x" % ord(c)
我得到了:
U+0061
U+0062
U+0063
U+20ac
U+10302
U+10fffd
Python 3.2:
s = "abc\u20ac\U00010302\U0010fffd"
for c in s:
print ("U+%04x" % ord(c))
对我来说是有效的:
U+0061
U+0062
U+0063
U+20ac
U+10302
U+10fffd
另外,我找到了一些信息,这个链接解释了这种行为是正常的。如果字符串是从文件等地方来的,可能需要先解码。
更新:
我发现了一个很有见地的解释在这里。内部的Unicode表示大小是一个编译时的选项,如果你要处理“宽”字符,超出了16位的范围,你需要自己编译Python来去掉这个限制,或者使用这个页面上的一些解决方法。显然,很多Linux发行版已经为你做了这些,就像我之前遇到的那样。
Python 通常会把 Unicode 值内部存储为 UCS2。UTF-32 中的字符 \U00010302 的 UTF-16 表示是 \UD800\UDF02,这就是你得到那个结果的原因。
不过,有些 Python 版本使用 UCS4,但这些版本之间不兼容。
你可以在 这里 查看更多信息。
Py_UNICODE 这个类型表示 Python 内部用来存储 Unicode 编码的方式。Python 默认的版本使用 16 位类型来表示 Py_UNICODE,并将 Unicode 值内部存储为 UCS2。也可以构建一个 UCS4 版本的 Python(大多数最新的 Linux 发行版都带有 UCS4 的 Python 版本)。这些版本会使用 32 位类型来表示 Py_UNICODE,并将 Unicode 数据内部存储为 UCS4。在支持 wchar_t 的平台上,并且与所选的 Python Unicode 版本兼容时,Py_UNICODE 是 wchar_t 的别名,以增强与本地平台的兼容性。在其他平台上,Py_UNICODE 则是无符号短整型(UCS2)或无符号长整型(UCS4)的别名。
在使用Python 3.2.1的窄Unicode版本时:
PythonWin 3.2.1 (default, Jul 10 2011, 21:51:15) [MSC v.1500 32 bit (Intel)] on win32.
Portions Copyright 1994-2008 Mark Hammond - see 'Help/About PythonWin' for further copyright information.
>>> import sys
>>> sys.maxunicode
65535
你发现的事情(UTF-16编码):
>>> s = "abc\u20ac\U00010302\U0010fffd"
>>> len(s)
8
>>> for c in s:
... print('U+{:04X}'.format(ord(c)))
...
U+0061
U+0062
U+0063
U+20AC
U+D800
U+DF02
U+DBFF
U+DFFD
解决方法:
>>> import struct
>>> s=s.encode('utf-32-be')
>>> struct.unpack('>{}L'.format(len(s)//4),s)
(97, 98, 99, 8364, 66306, 1114109)
>>> for i in struct.unpack('>{}L'.format(len(s)//4),s):
... print('U+{:04X}'.format(i))
...
U+0061
U+0062
U+0063
U+20AC
U+10302
U+10FFFD
关于Python 3.3的更新:
现在它的工作方式符合提问者的预期:
>>> s = "abc\u20ac\U00010302\U0010fffd"
>>> len(s)
6
>>> for c in s:
... print('U+{:04X}'.format(ord(c)))
...
U+0061
U+0062
U+0063
U+20AC
U+10302
U+10FFFD