在Python中创建漂亮的列输出

161

我希望能够在使用命令行管理工具时,在Python中创建一个漂亮的列列表。

基本上,我想要一个像这样的列表:

[['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']]

转换为:

a            b            c
aaaaaaaaaa   b            c
a            bbbbbbbbbb   c

使用普通的选项卡在这里行不通,因为我不知道每行中最长的数据。

这与Linux中的“column -t”行为相同。

$ echo -e "a b c\naaaaaaaaaa b c\na bbbbbbbbbb c"
a b c
aaaaaaaaaa b c
a bbbbbbbbbb c

$ echo -e "a b c\naaaaaaaaaa b c\na bbbbbbbbbb c" | column -t
a           b           c
aaaaaaaaaa  b           c
a           bbbbbbbbbb  c

我找了很多Python库来做这件事,但没有找到有用的东西。


4
使用ncurses来显示我所需的少量信息有些大材小用,但我们也在其他方面使用ncurses。 - xeor
22个回答

218

自从Python 2.6+以来,您可以使用以下方式的格式化字符串设置列的最小宽度为20个字符并将文本右对齐。

table_data = [
    ['a', 'b', 'c'],
    ['aaaaaaaaaa', 'b', 'c'], 
    ['a', 'bbbbbbbbbb', 'c']
]
for row in table_data:
    print("{: >20} {: >20} {: >20}".format(*row))

输出:

               a                    b                    c
      aaaaaaaaaa                    b                    c
               a           bbbbbbbbbb                    c

13
目前为止,这绝对是最好的解决方案。 - zlr
19
在KurzedMetal所提供的解决方案基础上,上述格式说明符中的">"表示右对齐。使用"{: <20}"将得到左对齐列,使用"{: ^20}"将得到居中对齐列。 - Dale Moore
2
如果您想将其与其他格式说明符(例如.2f)结合使用,则语法为{:>20.2f} - Dr_Zaszuś
3
我认为这并没有回答问题——OP似乎想要使每行的宽度不超过容纳其内容所需的宽度。这只是将固定宽度设置为20。 - intuited
3
适应f字符串:print(f'{i :>50} {j :>25} {k :>5}')(其中i,j,k是每行的三个值) - Alex
显示剩余3条评论

148
data = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']]

col_width = max(len(word) for row in data for word in row) + 2  # padding
for row in data:
    print "".join(word.ljust(col_width) for word in row)

a            b            c            
aaaaaaaaaa   b            c            
a            bbbbbbbbbb   c   

这段代码计算最长的数据项来确定列宽,然后使用 .ljust() 为每个列打印输出时添加必要的填充。


2
名称 longest 是误导性的,因为它不是最长的元素,而是最大长度。顺便说一句,可以使用类似以下代码获取最长的元素:max((w for sub in data for w in sub), key=len)。[附言:我不是那个给你点踩的人] - Rik Poggi
1
max((w for ...), key=len)可以得到最长的条目,然后您需要再运行一次len。我不能决定哪个更清晰,所以我坚持使用第一个。关于误导性变量名称的好建议,我已经进行了更改。 - Shawn Chin
1
是的,两者之间没有太大的区别,只是个人口味的问题而已。除此之外,正如你所注意到的,这行代码有点混乱(过于复杂)。最好直接用以下方式编写:max(len(x) for sub in data for x in sub),这样也不会生成不必要的列表。 - Rik Poggi
1
谢谢!这正是我需要的。但是,我也得让它能在 Python 2.4 上运行,所以我去掉了 chain.from_iterable,并用建议中的 max(len(x) for sub in data for x in sub) + 2 替换了 col_width。希望你能修改上面的代码,以便如果其他人想要在 2.4 上运行这个程序的话,代码也可以清晰地运行。 - xeor
3
这将使所有列的宽度相同,这不是 column -t 命令的作用。 - intuited
显示剩余2条评论

60

我带着同样的要求来到这里,但是@lvc和@Preet的回答似乎更符合column -t所产生的不同列宽的结果:

>>> rows =  [   ['a',           'b',            'c',    'd']
...         ,   ['aaaaaaaaaa',  'b',            'c',    'd']
...         ,   ['a',           'bbbbbbbbbb',   'c',    'd']
...         ]
...

>>> widths = [max(map(len, col)) for col in zip(*rows)]
>>> for row in rows:
...     print "  ".join((val.ljust(width) for val, width in zip(row, widths)))
...
a           b           c  d
aaaaaaaaaa  b           c  d
a           bbbbbbbbbb  c  d

4
好的。这是最清晰的解决方案,实际上遵循了原始“规范”。 - intuited
2
这是对我有效的解决方案。其他解决方案产生了列输出,但这个解决方案在填充控制和准确的列宽度方面提供了最多的控制。 - Michael J
2
优美的解决方案。对于任何非字符串列,只需添加一个额外的映射:map(len, map(str, col)) - Druckles
1
非常好的答案,谢谢!我将 print ... 更改为 print string.rstrip(...),这在终端中格式化得更好(由于文本换行,大量尾随空格可能显示为空白行)。 - 808sound
一种更简洁的消除最后一列(不可见)填充的方法是:widths[-1]=0 - DisappointedByUnaccountableMod

28

虽然来得有点晚,这也是我写的一个插件自吹自擂,但你也可以看看Columnar插件。

它需要一个输入列表的列表和一个标题列表,并输出格式化的表格字符串。 这个片段创建了一个类似于docker的表格:

from columnar import columnar

headers = ['name', 'id', 'host', 'notes']

data = [
    ['busybox', 'c3c37d5d-38d2-409f-8d02-600fd9d51239', 'linuxnode-1-292735', 'Test server.'],
    ['alpine-python', '6bb77855-0fda-45a9-b553-e19e1a795f1e', 'linuxnode-2-249253', 'The one that runs python.'],
    ['redis', 'afb648ba-ac97-4fb2-8953-9a5b5f39663e', 'linuxnode-3-3416918', 'For queues and stuff.'],
    ['app-server', 'b866cd0f-bf80-40c7-84e3-c40891ec68f9', 'linuxnode-4-295918', 'A popular destination.'],
    ['nginx', '76fea0f0-aa53-4911-b7e4-fae28c2e469b', 'linuxnode-5-292735', 'Traffic Cop'],
]

table = columnar(data, headers, no_borders=True)
print(table)

显示无边框样式的表格

或者您可以使用颜色和边框来让它更加复杂。 显示春季经典表格

要了解有关列大小调整算法的更多信息以及查看其余API,请访问上面的链接或查看Columnar GitHub Repo


13

哇,只有17个答案。Python之禅说:“最好有一种 -- 最好只有一种 -- 很明显的方法去做它。”

所以这里提供第18种方法: tabulate 包支持大量数据类型,可以将其显示为表格,这是从他们的文档中改编的一个简单示例:

from tabulate import tabulate

table = [["Sun",696000,1989100000],
         ["Earth",6371,5973.6],
         ["Moon",1737,73.5],
         ["Mars",3390,641.85]]

print(tabulate(table, headers=["Planet","R (km)", "mass (x 10^29 kg)"]))

输出

Planet      R (km)    mass (x 10^29 kg)
--------  --------  -------------------
Sun         696000           1.9891e+09
Earth         6371        5973.6
Moon          1737          73.5
Mars          3390         641.85

第一次看起来非常正确。它处理列表和列表的列表,使其非常易于使用。 - Stefatronik

9

将列进行转置是zip的工作:

>>> a = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']]
>>> list(zip(*a))
[('a', 'aaaaaaaaaa', 'a'), ('b', 'b', 'bbbbbbbbbb'), ('c', 'c', 'c')]

要找到每个列所需的长度,您可以使用max函数:
>>> trans_a = zip(*a)
>>> [max(len(c) for c in b) for b in trans_a]
[10, 10, 1]

您可以使用适当的填充来构建字符串,以便传递给print

>>> col_lenghts = [max(len(c) for c in b) for b in trans_a]
>>> padding = ' ' # You might want more
>>> padding.join(s.ljust(l) for s,l in zip(a[0], col_lenghts))
'a          b          c'

9

您需要通过两个步骤完成此操作:

  1. 获取每列的最大宽度。
  2. 使用第一步的最大宽度知识,使用 str.ljust()str.rjust() 进行列格式化。

short and sweet. - Sunny

6

使用pandas创建数据框的解决方案:

import pandas as pd
l = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']]
df = pd.DataFrame(l)

print(df)
            0           1  2
0           a           b  c
1  aaaaaaaaaa           b  c
2           a  bbbbbbbbbb  c

为了生成你想要的输出并删除索引和标题值,你可以使用to_string方法:
result = df.to_string(index=False, header=False)

print(result)
          a           b  c
 aaaaaaaaaa           b  c
          a  bbbbbbbbbb  c

6

要获得更漂亮的表格,如下所示:

---------------------------------------------------
| First Name | Last Name        | Age | Position  |
---------------------------------------------------
| John       | Smith            | 24  | Software  |
|            |                  |     | Engineer  |
---------------------------------------------------
| Mary       | Brohowski        | 23  | Sales     |
|            |                  |     | Manager   |
---------------------------------------------------
| Aristidis  | Papageorgopoulos | 28  | Senior    |
|            |                  |     | Reseacher |
---------------------------------------------------

您可以使用此Python recipe

'''
From http://code.activestate.com/recipes/267662-table-indentation/
PSF License
'''
import cStringIO,operator

def indent(rows, hasHeader=False, headerChar='-', delim=' | ', justify='left',
           separateRows=False, prefix='', postfix='', wrapfunc=lambda x:x):
    """Indents a table by column.
       - rows: A sequence of sequences of items, one sequence per row.
       - hasHeader: True if the first row consists of the columns' names.
       - headerChar: Character to be used for the row separator line
         (if hasHeader==True or separateRows==True).
       - delim: The column delimiter.
       - justify: Determines how are data justified in their column. 
         Valid values are 'left','right' and 'center'.
       - separateRows: True if rows are to be separated by a line
         of 'headerChar's.
       - prefix: A string prepended to each printed row.
       - postfix: A string appended to each printed row.
       - wrapfunc: A function f(text) for wrapping text; each element in
         the table is first wrapped by this function."""
    # closure for breaking logical rows to physical, using wrapfunc
    def rowWrapper(row):
        newRows = [wrapfunc(item).split('\n') for item in row]
        return [[substr or '' for substr in item] for item in map(None,*newRows)]
    # break each logical row into one or more physical ones
    logicalRows = [rowWrapper(row) for row in rows]
    # columns of physical rows
    columns = map(None,*reduce(operator.add,logicalRows))
    # get the maximum of each column by the string length of its items
    maxWidths = [max([len(str(item)) for item in column]) for column in columns]
    rowSeparator = headerChar * (len(prefix) + len(postfix) + sum(maxWidths) + \
                                 len(delim)*(len(maxWidths)-1))
    # select the appropriate justify method
    justify = {'center':str.center, 'right':str.rjust, 'left':str.ljust}[justify.lower()]
    output=cStringIO.StringIO()
    if separateRows: print >> output, rowSeparator
    for physicalRows in logicalRows:
        for row in physicalRows:
            print >> output, \
                prefix \
                + delim.join([justify(str(item),width) for (item,width) in zip(row,maxWidths)]) \
                + postfix
        if separateRows or hasHeader: print >> output, rowSeparator; hasHeader=False
    return output.getvalue()

# written by Mike Brown
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
def wrap_onspace(text, width):
    """
    A word-wrap function that preserves existing line breaks
    and most spaces in the text. Expects that existing line
    breaks are posix newlines (\n).
    """
    return reduce(lambda line, word, width=width: '%s%s%s' %
                  (line,
                   ' \n'[(len(line[line.rfind('\n')+1:])
                         + len(word.split('\n',1)[0]
                              ) >= width)],
                   word),
                  text.split(' ')
                 )

import re
def wrap_onspace_strict(text, width):
    """Similar to wrap_onspace, but enforces the width constraint:
       words longer than width are split."""
    wordRegex = re.compile(r'\S{'+str(width)+r',}')
    return wrap_onspace(wordRegex.sub(lambda m: wrap_always(m.group(),width),text),width)

import math
def wrap_always(text, width):
    """A simple word-wrap function that wraps text on exactly width characters.
       It doesn't split the text in words."""
    return '\n'.join([ text[width*i:width*(i+1)] \
                       for i in xrange(int(math.ceil(1.*len(text)/width))) ])

if __name__ == '__main__':
    labels = ('First Name', 'Last Name', 'Age', 'Position')
    data = \
    '''John,Smith,24,Software Engineer
       Mary,Brohowski,23,Sales Manager
       Aristidis,Papageorgopoulos,28,Senior Reseacher'''
    rows = [row.strip().split(',')  for row in data.splitlines()]

    print 'Without wrapping function\n'
    print indent([labels]+rows, hasHeader=True)
    # test indent with different wrapping functions
    width = 10
    for wrapper in (wrap_always,wrap_onspace,wrap_onspace_strict):
        print 'Wrapping function: %s(x,width=%d)\n' % (wrapper.__name__,width)
        print indent([labels]+rows, hasHeader=True, separateRows=True,
                     prefix='| ', postfix=' |',
                     wrapfunc=lambda x: wrapper(x,width))

    # output:
    #
    #Without wrapping function
    #
    #First Name | Last Name        | Age | Position         
    #-------------------------------------------------------
    #John       | Smith            | 24  | Software Engineer
    #Mary       | Brohowski        | 23  | Sales Manager    
    #Aristidis  | Papageorgopoulos | 28  | Senior Reseacher 
    #
    #Wrapping function: wrap_always(x,width=10)
    #
    #----------------------------------------------
    #| First Name | Last Name  | Age | Position   |
    #----------------------------------------------
    #| John       | Smith      | 24  | Software E |
    #|            |            |     | ngineer    |
    #----------------------------------------------
    #| Mary       | Brohowski  | 23  | Sales Mana |
    #|            |            |     | ger        |
    #----------------------------------------------
    #| Aristidis  | Papageorgo | 28  | Senior Res |
    #|            | poulos     |     | eacher     |
    #----------------------------------------------
    #
    #Wrapping function: wrap_onspace(x,width=10)
    #
    #---------------------------------------------------
    #| First Name | Last Name        | Age | Position  |
    #---------------------------------------------------
    #| John       | Smith            | 24  | Software  |
    #|            |                  |     | Engineer  |
    #---------------------------------------------------
    #| Mary       | Brohowski        | 23  | Sales     |
    #|            |                  |     | Manager   |
    #---------------------------------------------------
    #| Aristidis  | Papageorgopoulos | 28  | Senior    |
    #|            |                  |     | Reseacher |
    #---------------------------------------------------
    #
    #Wrapping function: wrap_onspace_strict(x,width=10)
    #
    #---------------------------------------------
    #| First Name | Last Name  | Age | Position  |
    #---------------------------------------------
    #| John       | Smith      | 24  | Software  |
    #|            |            |     | Engineer  |
    #---------------------------------------------
    #| Mary       | Brohowski  | 23  | Sales     |
    #|            |            |     | Manager   |
    #---------------------------------------------
    #| Aristidis  | Papageorgo | 28  | Senior    |
    #|            | poulos     |     | Reseacher |
    #---------------------------------------------

Python食谱页面上有一些改进。


3

Scolp是一个新的库,可以让你轻松地漂亮地打印流式列数据,同时自动调整列宽。

(免责声明:我是作者)


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