格式化包含非ascii字符的列

usu*_* me 8 python unicode string-formatting non-ascii-characters python-2.7

所以我想对齐包含非ascii字符的字段.以下似乎不起作用:

for word1, word2 in [['hello', 'world'], ['?????', '??']]:
    print "{:<20} {:<20}".format(word1, word2)

hello                world
?????      ??
Run Code Online (Sandbox Code Playgroud)

有解决方案吗?

Mar*_*ers 6

您正在格式化一个多字节编码的字符串。您似乎正在使用UTF-8编码文本,并且该编码在每个代码点使用多个字节(1到4之间,具体取决于特定字符)。格式化字符串会计算字节数,而不是代码点数,这是字符串最终未对齐的原因之一:

>>> len('hello')
5
>>> len('?????')
15
>>> len(u'?????')
5
Run Code Online (Sandbox Code Playgroud)

而是将文本格式设置为Unicode字符串,以便您可以计算代码点,而不是字节:

for word1, word2 in [[u'hello', u'world'], [u'?????', u'??']]:
    print u"{:<20} {:<20}".format(word1, word2)
Run Code Online (Sandbox Code Playgroud)

您的下一个问题是这些字符也比大多数字符。您有双倍宽的代码点:

>>> import unicodedata
>>> unicodedata.east_asian_width(u'h')
'Na'
>>> unicodedata.east_asian_width(u'?')
'W'
>>> for word1, word2 in [[u'hello', u'world'], [u'?????', u'??']]:
...     print u"{:<20} {:<20}".format(word1, word2)
...
hello                world
?????                ??
Run Code Online (Sandbox Code Playgroud)

str.format()没有能力处理该问题;您必须先根据Unicode标准中注册的字符数来手动调整列宽,然后再进行格式化。

这很棘手,因为有多个可用宽度。参见东亚宽度 Unicode标准附件 ; 有狭窄宽阔模棱两可的宽度;窄是大多数其他字符打印的宽度,宽是我终端上的宽度的两倍。含糊不清...关于实际显示的宽度含糊不清:

模糊字符需要字符代码中未包含的其他信息,才能进一步解析其宽度。

取决于上下文如何显示它们。例如,希腊字符在西方文本中显示为窄字符,但在东亚语境中显示为宽字符。我的终端将它们显示为窄,但是其他终端(例如,配置为东亚语言环境)则可能会将它们显示为宽。我不确定是否有万无一失的方法来弄清楚它是如何工作的。

在大多数情况下,您需要将带有'W''F'值的字符计数unicodedata.east_asian_width()为占据2个位置;从您的格式宽度中减去1:

def calc_width(target, text):
    return target - sum(unicodedata.east_asian_width(c) in 'WF' for c in text)

for word1, word2 in [[u'hello', u'world'], [u'?????', u'??']]:
    print u"{0:<{1}} {2:<{3}}".format(word1, calc_width(20, word1), word2, calc_width(20,  word2))
Run Code Online (Sandbox Code Playgroud)

然后在终端中产生所需的对齐方式:

>>> for word1, word2 in [[u'hello', u'world'], [u'?????', u'??']]:
...     print u"{0:<{1}} {2:<{3}}".format(word1, calc_width(20, word1), word2, calc_width(20,  word2))
...
hello                world
?????           ??
Run Code Online (Sandbox Code Playgroud)

可能在上面看到的轻微对齐错误是您的浏览器或字体对宽代码点使用了不同的宽度比率(不是很两倍)。

所有这些都带有一个警告:并非所有终端都支持East-Asian Width Unicode属性,并且仅以一个宽度显示所有代码点。