Unicode格式

时间:2018-10-22 13:56:23

标签: python python-unicode

我正在使用字符串格式。对于英语,格式是整洁的,但对于Unicode字符,格式是偶然的。谁能告诉我原因? 示例:

form = u'{:<15}{:<3}({})'
a = [
 u'സി ട്രീമിം',
 u'ബി ഡോഗേറ്റ്',
 u'ജെ ഹോളണ്ട്',
 u'എം നസീർ ',
 u'എം ബസ്ചാഗൻ…',
 u'ടി ഹെഡ് ',
 u'കെ ഭാരത് ',
 u'എം സിറാജ് ',
 u'എ ഈശ്വരൻ ',
 u'സി ഹാൻഡ്‌സ്‌കോംബ് ബി',]

 for i in range(0, 10):
     print form.format(a[i][:12], 1, 2)

给出的输出为 enter image description here

s = [
 u'abcdef',
 u'akash',
 u'rohit',
 u'anubhav',
 u'bhargav',
 u'achut',
 u'punnet',
 u'tom',
 u'rach',
 u'kamal'
 ]
for i in range(0, 10):
     print form.format(s[i][:12], 1, 2)

给予: enter image description here

2 个答案:

答案 0 :(得分:6)

您正在打印Malayalam Unicode codepoints,它使用大量的vowel signs来修改前面的字形。这些元音符号代码点本身不会形成新字母,并且Malayalam在终端中不会产生与ASCII字母相同的规则输出宽度。

例如,在您的第一个字符串中,以U+0D38 MALAYALAM LETTER SAU+0D3F MALAYALAM VOWEL SIGN I开头。第一个字母SA 在屏幕上占据完整位置,但是第二个字符元音符号I 在SA前面时会改变字母的打印方式。请注意,如何打印2个代码点,只有一个 可见字形:

>>> print u'\u0d38'  # letter SA
സ
>>> print u'\u0d3f'  # vowel sign I
 ി
>>> print u'\u0d38\u0d3f'  # both together
സി

马拉雅拉姆语代码点的宽度也不同;如果在SA和元音符号I下方分别添加和组合添加ASCII字母,则如下所示:

>>> print u'\u0d38\nA..\n\u0d3f\nB..\n\u0d38\u0d3f\nAB.'  # with ASCII letters for size
സ
A..
 ി
B..
സി
AB.

请注意的宽度比A的宽度(约2.5倍)宽,而സി的宽度几乎等于3个ASCII码点的固定宽度!但是,并非所有的马拉雅拉姆语字母都这么宽。第一个示例中的下一个字母是U+0D1F MALAYALAM LETTER TTA,它的宽度要小得多:

>>> print u'\u0d38\nA..\n\u0d1f\nB..'
സ
A..
ട
B..

在实践中,我希望差异没有关系,而是合并代码点,以使输出最终具有大致相同的宽度。

接下来,马拉雅拉姆语也有其他组合字符。您的第一个字符串为U+0D4D MALAYALAM SIGN VIRAMA,并与前面的字母TTA组合在一起。

变音符与前一个字母结合使用时,会严重影响打印宽度:

>>> print u'\u0d1f\nA..\n\u0d4d\nB..\n\u0d1f\u0d4d\nAB.'
ട
A..
 ്
B..
ട്
AB.

字母TTA与ASCII字母一样宽,当您添加virama符号时,宽度实际上并没有改变。

您可以通过查看代码点Unicode general categories来估计大小。 unicodedata.category() function为您提供了作为字符串的类别:

>>> import unicodedata
>>> unicodedata.category(u'\u0d38')
'Lo'
>>> unicodedata.category(u'\u0d3f')
'Mc'
>>> unicodedata.category(u'\u0d4d')
'Mn'

字母SA为Lo(字母,其他),元音符号为Mc(标记,空格组合),并且virama符号为Mn(标记,非间距)。

>>> categories = {}
>>> for c in a[0]:
...     cat = unicodedata.category(c)
...     categories[cat] = categories.get(cat, 0) + 1
... 
>>> categories
{'Lo': 4, 'Mn': 1, 'Mc': 4, 'Zs': 1}

因此,对于第一个字符串,有4个字母,4个组合标记和一个元音符号。 Zs类别(分隔符,空格)用于' ' ASCII空格字符。

如果跳过McMn字符,是否可以更好地预测其宽度?字符串a[0]的宽度为5个字符({{1}的4倍,空格为1):

Lo

在浏览器中,它看起来不够近,但是在我的iTerm终端窗口中,它看起来像这样:

Python 2.7 output printing the strings <code>സി ട്രീമിം</code> and <code>ABCDE.</code>, with the capital letters in the second string producing roughly the same width on the screen as the first line.

要使行对齐,必须为字符串计算正确的宽度,以为显示宽度和代码点数量的差异添加额外的空格:

>>> print a[0] + '\nABCDE.'
സി ട്രീമിം
ABCDE.

这已经大大改善了输出

Output on terminal with adjusted code; columns line up better but still too far apart

看来,那些较宽的字母确实会有所作为。您必须手动增加这些宽度,以获得更好的结果。通过从字母到调整宽度的映射,您可以使它再次对齐得更好。但是,代码点的宽度是由您使用的字体设置的,我不确定找到所有马拉雅拉姆语字母都使用相同宽度的字体有多么容易。

我发现仅使用制表符,使用

import unicodedata

def malayalam_width(s):
    return sum(1 for c in s if unicodedata.category(c)[0] != 'M')

form = u'{:<{width}}{:<3}({})'
for line in a:
    line = line[:12]
    adjust = len(line) - malayalam_width(line)
    print form.format(line, 1, 2, width=15 + adjust)

现在数字开始排列了:

Lined up columns with tabs

您确实需要不断调整宽度;否则,您会在一半的时间停在错误的制表位上。

注意:我对马拉雅拉姆语的文字一点都不熟悉,我肯定会错过关于各种字母,元音符号和变音符号如何相互作用的微妙之处。熟悉脚本和Unicode代码点的人可能会产生比我在这里介绍的更好的宽度近似函数。

我也忽略了最后一个字符串中当前存在的2个U+200C ZERO WIDTH NON-JOINER代码点;您可能要从数据中删除那些数据。顾名思义,它也没有宽度。

答案 1 :(得分:-1)

您可以使用wcwidth模块,它克服了在各种终端中对制表符长度进行不同解释的问题(据我所知)。

我在这里使用的是Python 3,我认为您使用的是2,所以您的里程可能会有所不同。另外,我修改了输出的格式,以演示一些正在使用的变量

解决方案

from wcwidth import wcswidth

a = [
    u'സി ട്രീമിം',
    u'ബി ഡോഗേറ്റ്',
    u'ജെ ഹോളണ്ട്',
    u'എം നസീർ ',
    u'എം ബസ്ചാഗൻ…',
    u'ടി ഹെഡ് ',
    u'കെ ഭാരത് ',
    u'എം സിറാജ് ',
    u'എ ഈശ്വരൻ ',
    u'സി ഹാൻഡ്‌സ്‌കോംബ് ബി'
]

desired = 15
max_str = 12

for item in a:

    sub_str = item[:max_str]

    diff = len(sub_str) - wcswidth(sub_str)

    indent = desired + diff if desired - wcswidth(sub_str) > 0 else desired + diff - 1

    form = u'{:<'+ str(indent) +'} {:<3}{:<3}{:<3}'

    print (form.format(sub_str, len(sub_str), wcswidth(sub_str), indent))

结果:

enter image description here