如何在Python 3中获得组合Unicode字符的显示宽度?

时间:2015-06-17 03:24:43

标签: python python-3.x unicode

在Python 3中,Unicode字符串应该为您提供Unicode字符的数量,但我无法弄清楚如何在某些字符组合的情况下获得字符串的最终显示宽度。

创世纪1:1 - בְּרֵאשִׁית,בָּרָאאֱלֹהִים,אֵתהַשָּׁמַיִם,וְאֵתהָאָרֶץ

load("./common.js")
print(leading_zero(3))
print(USERS)

但字符串只有37个字符宽。归一化不能解决问题,因为元音(较大字符下面的点)是不同的字符。

>>> len('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ')
60

作为旁注:>>> len(unicodedata.normalize('NFC', 'בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ')) 60 模块在​​这方面完全被打破,积极地包装它不应该的地方。 textwrap似乎也被打破了。

2 个答案:

答案 0 :(得分:4)

问题在于组合字符,Python在计算__len__时计算为不同,但合并为单个打印字符。

要确定某个字符是否为组合字符,我们可以使用unicodedata module

  

unicodedata.combining(unichr)

     

返回分配给Unicode字符unichr的规范组合类作为整数。如果没有定义组合类,则返回0。

一个天真的解决方案是删除任何具有非零组合类的字符。这会留下独立的字符,并且应该为我们提供一个字符串,其中包含可见字符和底层字符之间的1对1映射。 (我是一个Unicode新手,它可能比那更复杂。结合字符和字形扩展器有一些细微之处我不太懂,但对于这个特定的字符串似乎并不重要。)

所以我想出了这个功能:

import unicodedata

def visible_length(unistr):
    '''Returns the number of printed characters in a Unicode string.'''
    return len([char for char in unistr if unicodedata.combining(char) == 0])

返回字符串的正确长度:

>>> visible_length('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ')
37

这可能不是所有Unicode字符串的完整解决方案,但根据您正在使用的Unicode子集,这可能足以满足您的需求。

答案 1 :(得分:3)

使用第三方uniseg的几个解决方案,如@bobince所建议的那样:

>>> from uniseg.graphemecluster import grapheme_cluster_breakables
>>> sum(grapheme_cluster_breakables('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ'))
37
>>>
>>> from uniseg.graphemecluster import grapheme_clusters
>>> list(grapheme_clusters('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְ  הָאָרֶץ'))
['בְּ', 'רֵ', 'א', 'שִׁ', 'י', 'ת', ',', ' ', 'בָּ', 'רָ', 'א', ' ', 'אֱ', 'לֹ', 'הִ', 'י', 'ם', ',', ' ', 'אֵ', 'ת', ' ', 'הַ', 'שָּׁ', 'מַ', 'יִ', 'ם', ',', ' ', 'וְ', 'אֵ', 'ת', ' ', 'הָ', 'אָ', 'רֶ', 'ץ']
>>> len(list(grapheme_clusters('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַי , ואֵת הָאָרֶץ')))
37

这看起来是正确的方法。

以下是修补textwrap的示例。修补其他模块的解决方案应该类似。

>>> import textwrap
>>> text = 'בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשּׁמַיִם, וְאֵת הָאָרֶץ'
>>> print(textwrap.fill(text, width=40))  # bad, aggressive wrapping
בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת
הַשָּׁמַיִם, וְאֵת הָאָרֶץ
>>> import uniseg.graphemecluster
>>> def new_len(x):
...     if isinstance(x, str):
...         return sum(1 for _ in uniseg.graphemecluster.grapheme_clusters(x))
...     return len(x)
>>> textwrap.len = new_len
>>> print(textwrap.fill(text, width=40))  # Good wrapping
בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ