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

Con*_*ens 11 python unicode python-3.x

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

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

>>> len('???????????, ?????? ????????, ??? ???????????, ????? ???????')
60
Run Code Online (Sandbox Code Playgroud)

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

>>> len(unicodedata.normalize('NFC', '???????????, ?????? ????????, ??? ???????????, ????? ???????'))
60
Run Code Online (Sandbox Code Playgroud)

作为旁注:textwrap在这方面,模块完全被打破,积极地包裹它不应该的地方. str.format似乎同样破碎了.

ale*_*han 5

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

要确定一个字符是否是组合字符,我们可以使用unicodedata 模块

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])
Run Code Online (Sandbox Code Playgroud)

它为您的字符串返回正确的长度:

>>> visible_length('???????????, ?????? ????????, ??? ???????????, ????? ???????')
37
Run Code Online (Sandbox Code Playgroud)

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

  • 如果您需要完整的 Unicode 字素簇分割算法或行拆分,那就有点复杂了——请参阅第三方模块,例如 uniseg。 (3认同)

Con*_*ens 5

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

\n\n
>>> from uniseg.graphemecluster import grapheme_cluster_breakables\n>>> sum(grapheme_cluster_breakables('\xd7\x91\xd6\xbc\xd6\xb0\xd7\xa8\xd6\xb5\xd7\x90\xd7\xa9\xd7\x81\xd6\xb4\xd7\x99\xd7\xaa, \xd7\x91\xd6\xbc\xd6\xb8\xd7\xa8\xd6\xb8\xd7\x90 \xd7\x90\xd6\xb1\xd7\x9c\xd6\xb9\xd7\x94\xd6\xb4\xd7\x99\xd7\x9d, \xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb7\xd7\xa9\xd6\xbc\xd7\x81\xd6\xb8\xd7\x9e\xd6\xb7\xd7\x99\xd6\xb4\xd7\x9d, \xd7\x95\xd6\xb0\xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb8\xd7\x90\xd6\xb8\xd7\xa8\xd6\xb6\xd7\xa5'))\n37\n>>>\n>>> from uniseg.graphemecluster import grapheme_clusters\n>>> list(grapheme_clusters('\xd7\x91\xd6\xbc\xd6\xb0\xd7\xa8\xd6\xb5\xd7\x90\xd7\xa9\xd7\x81\xd6\xb4\xd7\x99\xd7\xaa, \xd7\x91\xd6\xbc\xd6\xb8\xd7\xa8\xd6\xb8\xd7\x90 \xd7\x90\xd6\xb1\xd7\x9c\xd6\xb9\xd7\x94\xd6\xb4\xd7\x99\xd7\x9d, \xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb7\xd7\xa9\xd6\xbc\xd7\x81\xd6\xb8\xd7\x9e\xd6\xb7\xd7\x99\xd6\xb4\xd7\x9d, \xd7\x95\xd6\xb0  \xd7\x94\xd6\xb8\xd7\x90\xd6\xb8\xd7\xa8\xd6\xb6\xd7\xa5'))\n['\xd7\x91\xd6\xbc\xd6\xb0', '\xd7\xa8\xd6\xb5', '\xd7\x90', '\xd7\xa9\xd7\x81\xd6\xb4', '\xd7\x99', '\xd7\xaa', ',', ' ', '\xd7\x91\xd6\xbc\xd6\xb8', '\xd7\xa8\xd6\xb8', '\xd7\x90', ' ', '\xd7\x90\xd6\xb1', '\xd7\x9c\xd6\xb9', '\xd7\x94\xd6\xb4', '\xd7\x99', '\xd7\x9d', ',', ' ', '\xd7\x90\xd6\xb5', '\xd7\xaa', ' ', '\xd7\x94\xd6\xb7', '\xd7\xa9\xd6\xbc\xd7\x81\xd6\xb8', '\xd7\x9e\xd6\xb7', '\xd7\x99\xd6\xb4', '\xd7\x9d', ',', ' ', '\xd7\x95\xd6\xb0', '\xd7\x90\xd6\xb5', '\xd7\xaa', ' ', '\xd7\x94\xd6\xb8', '\xd7\x90\xd6\xb8', '\xd7\xa8\xd6\xb6', '\xd7\xa5']\n>>> len(list(grapheme_clusters('\xd7\x91\xd6\xbc\xd6\xb0\xd7\xa8\xd6\xb5\xd7\x90\xd7\xa9\xd7\x81\xd6\xb4\xd7\x99\xd7\xaa, \xd7\x91\xd6\xbc\xd6\xb8\xd7\xa8\xd6\xb8\xd7\x90 \xd7\x90\xd6\xb1\xd7\x9c\xd6\xb9\xd7\x94\xd6\xb4\xd7\x99\xd7\x9d, \xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb7\xd7\xa9\xd6\xbc\xd7\x81\xd6\xb8\xd7\x9e\xd6\xb7\xd7\x99 , \xd7\x95\xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb8\xd7\x90\xd6\xb8\xd7\xa8\xd6\xb6\xd7\xa5')))\n37\n
Run Code Online (Sandbox Code Playgroud)\n\n

这看起来是正确的方法。

\n\n

这是一个修补的例子textwrap。修补其他模块的解决方案应该类似。

\n\n
>>> import textwrap\n>>> text = '\xd7\x91\xd6\xbc\xd6\xb0\xd7\xa8\xd6\xb5\xd7\x90\xd7\xa9\xd7\x81\xd6\xb4\xd7\x99\xd7\xaa, \xd7\x91\xd6\xbc\xd6\xb8\xd7\xa8\xd6\xb8\xd7\x90 \xd7\x90\xd6\xb1\xd7\x9c\xd6\xb9\xd7\x94\xd6\xb4\xd7\x99\xd7\x9d, \xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb7\xd7\xa9\xd6\xbc\xd7\x81\xd7\x9e\xd6\xb7\xd7\x99\xd6\xb4\xd7\x9d, \xd7\x95\xd6\xb0\xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb8\xd7\x90\xd6\xb8\xd7\xa8\xd6\xb6\xd7\xa5'\n>>> print(textwrap.fill(text, width=40))  # bad, aggressive wrapping\n\xd7\x91\xd6\xbc\xd6\xb0\xd7\xa8\xd6\xb5\xd7\x90\xd7\xa9\xd7\x81\xd6\xb4\xd7\x99\xd7\xaa, \xd7\x91\xd6\xbc\xd6\xb8\xd7\xa8\xd6\xb8\xd7\x90 \xd7\x90\xd6\xb1\xd7\x9c\xd6\xb9\xd7\x94\xd6\xb4\xd7\x99\xd7\x9d, \xd7\x90\xd6\xb5\xd7\xaa\n\xd7\x94\xd6\xb7\xd7\xa9\xd6\xbc\xd7\x81\xd6\xb8\xd7\x9e\xd6\xb7\xd7\x99\xd6\xb4\xd7\x9d, \xd7\x95\xd6\xb0\xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb8\xd7\x90\xd6\xb8\xd7\xa8\xd6\xb6\xd7\xa5\n>>> import uniseg.graphemecluster\n>>> def new_len(x):\n...     if isinstance(x, str):\n...         return sum(1 for _ in uniseg.graphemecluster.grapheme_clusters(x))\n...     return len(x)\n>>> textwrap.len = new_len\n>>> print(textwrap.fill(text, width=40))  # Good wrapping\n\xd7\x91\xd6\xbc\xd6\xb0\xd7\xa8\xd6\xb5\xd7\x90\xd7\xa9\xd7\x81\xd6\xb4\xd7\x99\xd7\xaa, \xd7\x91\xd6\xbc\xd6\xb8\xd7\xa8\xd6\xb8\xd7\x90 \xd7\x90\xd6\xb1\xd7\x9c\xd6\xb9\xd7\x94\xd6\xb4\xd7\x99\xd7\x9d, \xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb7\xd7\xa9\xd6\xbc\xd7\x81\xd6\xb8\xd7\x9e\xd6\xb7\xd7\x99\xd6\xb4\xd7\x9d, \xd7\x95\xd6\xb0\xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb8\xd7\x90\xd6\xb8\xd7\xa8\xd6\xb6\xd7\xa5\n
Run Code Online (Sandbox Code Playgroud)\n

  • 您还可以使用 `regex` 模块: `count_user_perceived_characters = lambda text: len(regex.findall(r'\X', text))` (3认同)