首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >格式化包含非ascii字符的列

格式化包含非ascii字符的列
EN

Stack Overflow用户
提问于 2016-01-07 12:38:38
回答 2查看 910关注 0票数 10

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

代码语言:javascript
代码运行次数:0
运行
复制
for word1, word2 in [['hello', 'world'], ['こんにちは', '世界']]:
    print "{:<20} {:<20}".format(word1, word2)

hello                world
こんにちは      世界

有解决办法吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-01-07 12:47:45

您正在格式化多字节编码的字符串。您似乎正在使用UTF-8对您的文本进行编码,而且编码每个代码点使用多个字节(根据具体字符的不同,在1到4之间)。格式化字符串会计算字节,而不是代码点,这也是字符串最终对齐错误的原因之一:

代码语言:javascript
代码运行次数:0
运行
复制
>>> len('hello')
5
>>> len('こんにちは')
15
>>> len(u'こんにちは')
5

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

代码语言:javascript
代码运行次数:0
运行
复制
for word1, word2 in [[u'hello', u'world'], [u'こんにちは', u'世界']]:
    print u"{:<20} {:<20}".format(word1, word2)

您的下一个问题是,这些字符也比大多数字符更宽;您有两个范围的代码点:

代码语言:javascript
代码运行次数:0
运行
复制
>>> 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
こんにちは                世界

str.format()不具备处理这个问题的能力;在格式化之前,您必须根据在Unicode标准中注册为更宽的字符数量来手动调整列宽。

这很棘手,因为有一个以上的宽度可用。查看Unicode标准附件;有窄的、宽的和的双倍宽度;窄是大多数其他字符打印的宽度,宽是我终端上的两倍。暧昧是..。对于实际显示的宽度不明确:

含糊不清的字符需要字符代码中没有包含的其他信息来进一步解析它们的宽度。

这取决于上下文的显示方式;例如,希腊字符在西方文本中显示为狭窄字符,而在东亚上下文中则显示为宽字符。我的终端显示它们很窄,但其他终端(例如,为东亚地区配置的)可能会显示它们的宽度。我不确定是否有任何防止愚昧的方法来弄清楚那是怎么回事。

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

代码语言:javascript
代码运行次数:0
运行
复制
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))

这将在我的终端中产生所需的对齐。

代码语言:javascript
代码运行次数:0
运行
复制
>>> 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
こんにちは           世界

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

所有这些都附带了一个警告:并不是所有的终端都支持东亚宽度Unicode属性,并且只在一个宽度上显示所有代码点。

票数 8
EN

Stack Overflow用户

发布于 2016-01-07 12:59:12

这不是一项简单的任务--这不是简单的“非ascii”--它们是宽unicode字符,它们的显示非常棘手--从根本上讲,这更多地取决于您使用的终端类型,而不是您在其中放置的空格数。

首先,您必须使用UNICODE字符串。由于您在Python 2中,这意味着您应该在文本引号前加上"u“。

代码语言:javascript
代码运行次数:0
运行
复制
for word1, word2 in [[u'hello', u'world'], [u'こんにちは', u'世界']]:
    print "{:<20} {:<20}".format(word1, word2)

这样,Python实际上可以将字符串中的每个字符识别为一个字符,而不是一个字节集合,这些字节由于偶然的原因而被显示出来。

代码语言:javascript
代码运行次数:0
运行
复制
>>> a = u'こんにちは'
>>> len(a)
5
>>> b = 'こんにちは'
>>> len(b)
15

乍一看,这些长度似乎可以用来计算字符宽度。不幸的是,utf-8编码字符的字节长度与字符的实际显示宽度无关。单宽度unicode字符也是utf-8中的多字节字符(如ç)。

现在,在讨论unicode之后,Python确实包含了一些实用工具--包括一个函数调用,以了解每个unicode的显示单元是什么--字符--它是unicode.east_asian_width --这允许您有一种方法来计算每个字符串的宽度,然后有适当的空格数:

自动计算“{:

代码语言:javascript
代码运行次数:0
运行
复制
import unicode

def display_len(text):
    res = 0
    for char in text:
        res += 2 if unicodedata.east_asian_width(char) == 'W' else 1
    return res

for word1, word2 in [[u'hello', u'world'], [u'こんにちは', u'世界']]:
    width_format = u"{{}}{}{{}}".format(" " * (20 - (display_len(word1))))
    print width_format.format(word1, word2)

在我的终端机上对我起了作用:

代码语言:javascript
代码运行次数:0
运行
复制
hello              world
こんにちは          世界

但正如Martijn所说,它比这更复杂。有歧义字符和终端类型。如果您确实需要在文本终端中对齐这个文本,那么您应该使用终端库,比如诅咒,它允许您指定一个显示坐标来打印字符串。这样,在打印每个单词之前,您可以简单地在适当的列上明确地定位光标,并避免所有显示宽度的计算。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/34655347

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档