【问题标题】:How do you get the display width of combined Unicode characters in Python 3?如何在 Python 3 中获得组合 Unicode 字符的显示宽度?
【发布时间】:2015-09-02 02:40:44
【问题描述】:

在 Python 3 中,Unicode 字符串应该给你 Unicode 字符的数量,但鉴于某些字符组合,我无法弄清楚如何获得字符串的最终显示宽度。

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

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

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

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

附带说明:textwrap 模块在这方面完全被破坏了,在不应该的地方积极地包装。 str.format 似乎同样坏掉了。

【问题讨论】:

标签: python python-3.x unicode


【解决方案1】:

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

要判断一个字符是否是组合字符,我们可以使用unicodedata module

unicodedata.<strong>combining</strong>(<em>unichr</em>)

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

一个天真的解决方案是用非零组合类去除任何字符。这留下了独立存在的字符,并且应该给我们一个字符串,该字符串在可见字符和底层字符之间具有一对一的映射。 (我是 Unicode 新手,它可能比这更复杂。组合字符和字素扩展器有一些微妙之处,我不太了解,但对于这个特定的字符串似乎无关紧要。) em>

所以我想出了这个功能:

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 子集,这可能足以满足您的需求。

【讨论】:

  • 如果您需要完整的 Unicode 字素簇分割算法或行分割,那就有点复杂了——请参阅 uniseg 等第三方模块。
  • +1。这发生在我身上,但是当我使用 unicodedata.combining 并看到它返回的值范围很广时,我非常害怕,但也许它适合我的目的。谢谢。希望有人可以提出更强大的解决方案。
【解决方案2】:

@bobince 建议的使用第三方uniseg 的几个解决方案:

>>> 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
בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ

【讨论】:

  • 你也可以使用regex模块:count_user_perceived_characters = lambda text: len(regex.findall(r'\X', text))
  • @J.F.Sebastian 整洁!该项目表示它打算替换re。你知道它是否真的会吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-13
  • 2011-12-04
  • 2011-10-18
  • 2012-03-10
  • 2017-01-30
  • 2022-07-16
相关资源
最近更新 更多