如何在Python 3中遍历Unicode字符?

13 投票
3 回答
6208 浏览
提问于 2025-04-17 02:44

我需要逐个字符地处理一个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 个回答

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发行版已经为你做了这些,就像我之前遇到的那样。

3

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)的别名。

7

在使用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

撰写回答