在Python中,如何获取组合Unicode字符串的“可见”长度?

13
如果我有一个包含组合字符的Python Unicode字符串,len返回的值与看到的字符数量不符。例如,如果我有一个包含上划线和下划线等组合号的字符串,比如u'A\u0332\u0305BC',那么len(u'A\u0332\u0305BC')将报告为5;但显示的字符串只有3个字符长。如何在Python中获取包含组合字形的Unicode字符串的“可见”长度——即用户看到的字符串占用的不同位置的数量?

1
嗯,这很有趣,我能想到的最好方法就是去除不需要的字符。 - postelrich
1
@riotburn:这将会很困难。字符可能是任意的(由用户提供)。我需要查阅一个Unicode字形组合列表 - 除非这是编码的系统化部分。 - orome
3个回答

5
如果您使用支持匹配字素的正则表达式,可以使用 \X示例演示 尽管默认的 Python re 模块不支持 \X,但 Matthew Barnett 的 regex 模块 支持。
>>> len(regex.findall(r'\X', u'A\u0332\u0305BC'))
3

在Python 2中,您需要在模式中使用u
>>> regex.findall(u'\\X', u'A\u0332\u0305BC')
[u'A\u0332\u0305', u'B', u'C']
>>> len(regex.findall(u'\\X', u'A\u0332\u0305BC'))
3

4

unicodedata 模块 中有一个名为 combining 的函数,它可以用于判断单个字符是否是组合字符。如果返回值为 0,则可以将该字符视为非组合字符。

import unicodedata
len(u''.join(ch for ch in u'A\u0332\u0305BC' if unicodedata.combining(ch) == 0))

或者,稍微简单一些:
sum(1 for ch in u'A\u0332\u0305BC' if unicodedata.combining(ch) == 0)

1
Or:sum(not unicodedata.combining(ch) for ch in u'A\u0332\u0305BC') - Bakuriu
@Bakuriu,一开始我认为这不会起作用,因为“combining”返回的整数不是0或1,但“not”解决了这个问题。做得好! - Mark Ransom
3
对于由非标记字符组成的字形簇,例如:u'\u1100\u1161\u11A8'(각),此方法无效。 - 一二三
@一二三 在unicodedata中是否还有其他可以处理这种情况的内容? - Mark Ransom
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - 一二三
顺便提一下,0 是假值,所以你可以使用 not 代替 == 0,像这样 len(u''.join(ch for ch in u'A\u0332\u0305BC' if not unicodedata.combining(ch))) - wjandrea

3

组合字符并不是唯一的零宽字符:

>>> sum(1 for ch in u'\u200c' if unicodedata.combining(ch) == 0)
1

("\u200c""‌"是零宽度非连接符,它是一个不可打印的字符。)

在这种情况下,正则表达式模块也无法工作:

>>> len(regex.findall(r'\X', u'\u200c'))
1

我发现了一个名为 wcwidth 的工具,可以正确处理上述情况。
>>> from wcwidth import wcswidth
>>> wcswidth(u'A\u0332\u0305BC')
3
>>> wcswidth(u'\u200c')
0

但似乎仍然无法与用户596219的示例配合使用:
>>> wcswidth('각')
4

1
正则表达式模块在Python 3.7中有一些关于零宽度匹配的更新,所以现在可能会正常工作。我自己还没有尝试过。 - wjandrea

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