Python的 `str.format()`、填充字符和ANSI颜色

16
在Python 2中,我使用str.format()来对齐一堆要打印到终端的文本列。基本上,它是一个表格,但我不打印任何边框或其他东西-它只是一行行的文本,对齐成列。
  • 没有颜色处理时,一切都会按预期打印。
  • 如果我使用ANSI颜色代码包装整个行(即一个print语句),则一切都会按预期打印。
  • 然而:如果我尝试使一行中的每个列都有不同的颜色,则对齐会被破坏。从技术上讲,对齐被保留了;就是填充字符(空格)并未按预期打印; 实际上,填充字符似乎被完全移除了。

我已经验证了使用coloramaxtermcolor都存在同样的问题。结果也相同。因此,我确信这个问题与str.format()在字符串中间与ANSI转义序列不兼容有关。

但我不知道该怎么办啊! :( 如果可以在不手动对齐每个文本列的情况下完成这个任务,那将意义重大。

颜色和对齐是提高可读性的有力工具,而可读性是软件可用性的重要组成部分。如果能够解决此问题,那就太好了!

帮帮我?☺


如果你想得到比“Python .format()对ANSI转义字符没有任何不同”的更具体的信息,你需要向我们展示(部分)代码。 - Martijn Pieters
我明白如果没有代码本身,帮助起来会更加困难。很抱歉。我希望能得到像@MartijnPieters在下面发布的答案。如果我没有运气得到那个答案,我会继续发布代码。 - Zearin
请参见https://dev59.com/a3I95IYBdhLWcg3wtwVe。 - Brian Burns
3个回答

12

这是一个非常晚的答案,留下面包屑给那些在尝试使用内置ANSI颜色代码格式化文本时遇到困难的人。

byoungb的评论关于根据预先着色的文本长度作出填充决策是完全正确的。但如果您已经有了彩色文本,这里有一个解决方法:

请查看我在PyPI上发布的ansiwrap模块。它的主要目的是为ANSI彩色文本提供textwrap,但它还导出了ansilen(),它会告诉您“如果此字符串不包含ANSI控制代码,则其长度将是多少?” 它非常有用,可以在预先着色的文本中进行格式化、列宽和换行决策。在s的末尾或开头添加width - ansilen(s)个空格,以左(或右)对齐您所需的width列中的s。例如:

def ansi_ljust(s, width):
    needed = width - ansilen(s)
    if needed > 0:
        return s + ' ' * needed
    else:
        return s

此外,如果您需要在某个时候拆分、截断或组合彩色文本,则会发现 ANSI 的状态性质使得这成为一项繁琐的工作。您可能会发现 ansi_terminate_lines() 很有用;它可以“修补”子字符串列表,使其具有独立的、自立的 ANSI 代码,其效果与原始字符串相同。

ansicolors 的最新版本还包含了一个等价实现 ansilen()


1
感谢ansiwrap模块!对于像我一样在使用prettytable时遇到终端着色问题的人,只需编辑prettytable.py并将import textwrap替换为import ansiwrap as textwrap即可。 - ILoveGit
五年后的更新:ansiwrap 已经有一段时间没有得到维护了,并且已经在几年内不断发出弃用通知(https://github.com/jonathaneunice/ansiwrap/issues/17)。这对我维护的某个应用程序的一些包管理器造成了问题,因为它依赖于它。 - Micah

7

Python不区分“普通”字符和ANSI颜色代码,这些代码也是终端解释的字符。

换句话说,在终端打印'\x1b[92m'可能会改变终端文本颜色,但Python只将其视为一组5个字符。如果你使用print repr(line),Python会打印字符串的文字形式,包括使用非ASCII可打印字符的转义码(因此ESC ASCII码27会显示为\x1b),以查看添加了多少个字符。

你需要手动调整列对齐方式,以允许这些额外的字符。

不过,如果没有您实际的代码,我们很难帮助您。


啊,有趣!我会尝试一下,看看我能做什么。如果我没有成功,我会发布代码的。但如果可能的话,我想避免这样做,因为它很混乱,而且我有点尴尬。嘘! - Zearin
4
我发现对传递给你的字符串进行填充更容易着色。如果您没有背景颜色,或者不关心填充是否应用了背景颜色,那么这种方法就有效。翻译后的代码如下: stdout.write('{:s}\t [{:s}]\n'.format(x, colorize('{:^10}'.format(y).encode('utf-8').decode('unicode_escape'), 'red').encode('ascii', 'ignore').decode('ascii'))) - byoungb

1

我也来晚了。曾经遇到过处理颜色和对齐的同样问题。这里是我编写的一个函数,它为字符串添加填充,该字符串默认情况下具有“不可见”的字符,例如转义序列。

def ljustcolor(text: str, padding: int, char=" ") -> str:
    import re
    pattern = r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]'

    matches = re.findall(pattern, text)
    offset = sum(len(match) for match in matches)
    return text.ljust(padding + offset,char[0])

该模式匹配所有ANSI转义序列,包括颜色代码。然后我们获取所有匹配项的总长度,这将作为我们添加到ljust中的填充值的偏移量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接