如何在 Python 3 中迭代 Unicode 字符?

Posted

技术标签:

【中文标题】如何在 Python 3 中迭代 Unicode 字符?【英文标题】:How to iterate over Unicode characters in Python 3? 【发布时间】:2011-11-21 14:02:14 【问题描述】:

我需要一次遍历一个字符的 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”类型的论点。)

【问题讨论】:

我能做的最好的事情(在 Python 2 上,像你一样狭窄的构建)是 string.encode('utf-32-be') 然后 for chars in (string[n:n+4] for n in range(0, len(string), 4)): 然后 code = reduce(lambda x, y: (x &lt;&lt; 8) + y, (ord(ch) for ch in chars)) 我认为自己在精确的 Unicode 术语方面是一个迂腐的吹毛求疵者,并认为你已经把自己说得很清楚了 ;-) sys.maxunicode 是“一个整数,给出了 Unicode 字符支持的最大代码点。”如果您使用的是 UTF-16 版本的 Python,非 BMP 字符可能不支持 unicode 字符串迭代。我在***.com/questions/7495150/… 提出了这个问题。 【参考方案1】:

在具有窄 Unicode 构建的 Python 3.2.1 上:

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 更新:

现在它按照 OP 期望的方式工作:

>>> 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

【讨论】:

感谢 struct.unpack() 技巧;我不知道你能做到这一点,而且它比我的代码短得多。我认为现在很清楚这是我将获得的最佳解决方案;显然 Python 本身不支持 UTF-32(自定义 32 位构建之外)。【参考方案2】:

Python 通常在内部将 unicode 值存储为 UCS2。 UTF-32 \U00010302 字符的 UTF-16 表示是 \UD800\UDF02,这就是你得到这个结果的原因。

也就是说,有一些使用 UCS4 的 python 构建,但这些构建彼此不兼容。

看看here。

Py_UNICODE 此类型表示 Python 在内部用作保存 Unicode 序数的基础的存储类型。 Python 的默认构建对 Py_UNICODE 使用 16 位类型,并将 Unicode 值在内部存储为 UCS2。也可以构建 Python 的 UCS4 版本(最近的 Linux 发行版带有 Python 的 UCS4 版本)。然后,这些构建对 Py_UNICODE 使用 32 位类型,并将 Unicode 数据在内部存储为 UCS4。在 wchar_t 可用且与所选 Python Unicode 构建变体兼容的平台上,Py_UNICODE 是 wchar_t 的 typedef 别名,以增强本机平台兼容性。在所有其他平台上,Py_UNICODE 是 unsigned short (UCS2) 或 unsigned long (UCS4) 的 typedef 别名。

【讨论】:

是的,这些我都知道。我以为我在原始帖子中已经明确表示我完全理解为什么我会看到我所看到的。我正在寻找一种获取 UTF-32 的方法,而不是对 UTF-16 工作原理的另一种解释。 对不起罗斯。我从您的问题中了解到您知道 UTF16 和 UTF-32 是如何工作的。从你的名声我可以看出你并不天真。关键是,一旦您的 python 构建使用 UCS2 编码,这就是您在请求代码点时得到的。如果你使用的是 UCS4 python 构建,你会得到另一个。所以,我认为您在这里真正想要的是找出如何将 UFT-16 转换为 UFT-32,而不管源代码的原始情况如何。 是的,完全正确。好吧,确切地说,如何将 Python 3 字符串转换为 UTF-32,而不管它碰巧在内部运行的 Python 构建使用哪种编码(UTF-16 或 32)。 @RossSmith:也许将最后一部分(“不管 Python 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

此外,我发现 this link 解释了该行为正常工作。如果字符串来自文件等,可能需要先解码。

更新

我找到了一个很有见地的explanation here。内部 Unicode 表示大小是一个编译时选项,如果使用 16 位平面之外的“宽”字符,您需要自己构建 python 以消除限制,或使用此页面上的解决方法之一。显然,正如我在上面遇到的那样,许多 Linux 发行版已经为您做到了这一点。

【讨论】:

在 Python 3.2.1 中没有做到这一点。 sys.maxunicode 在您的系统上的值是多少?也许你有一个广泛的 Unicode 版本? Gringo,您显然有一个内部使用 UTF-32 的 Python 版本。正如 pablosaraiva 指出的那样,这不是默认设置,不能在可移植代码中依赖。 有趣:import sys; sys.maxunicode 1114111。我正在使用由 Ubuntu Natty (debian) 打包的 Python。我怀疑您的设置更“自定义”...... Windows 一直更喜欢不太常见的 16 位变体。我本来希望 python 像 int/long 一样透明地处理诸如此类的细节(例如)。 我使用的是标准的 Windows 发行版,直接来自 Python 官方网站。 (我刚刚在官方 Mac 版本中尝试过,它也使用 UTF-16。)我还希望这可以透明地处理;不幸的是,Guido 显然不同意。 是的,(尽管 UCS 不是 UTF)每天都能学到新东西。我已经链接到一个很好的解释的答案。也许他们认为 UCS-4 浪费了太多空间。

以上是关于如何在 Python 3 中迭代 Unicode 字符?的主要内容,如果未能解决你的问题,请参考以下文章

第四周Python--装饰器(迭代器生成器)

Python 3.6:如何使用 unicode/utf-8 文件名保存文件? [关闭]

如何将 Python 2 unicode() 函数转换为正确的 Python 3.x 语法

unicode 在 Python 内部是如何表示的?

2 Unicode 和list 列表

python 3.0,如何使 print() 输出 unicode?