在 python 中创建良好的列输出

我试图在 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 库来实现这一点,但是没有找到任何有用的东西。

311789 次浏览

你必须做到这一点,两个通行证:

  1. 得到每列的最大宽度。
  2. 格式化列使用我们的知识最大宽度从第一次通过使用 str.ljust()str.rjust()
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()在打印出每列时添加必要的填充。

像这样调整列是 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'

自 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

我带着同样的要求来到这里,但@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

为了得到更漂亮的桌子

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

你可使用以下 巨蟒配方:

'''
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 食谱页面包含了一些改进。

基于 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

我知道这个问题已经很老了,但是我不明白 Antak 的答案,也不想使用库,所以我自己制作了一个解决方案。

解决方案假定记录是2D 数组,记录的长度都相同,并且字段都是字符串。

def stringifyRecords(records):
column_widths = [0] * len(records[0])
for record in records:
for i, field in enumerate(record):
width = len(field)
if width > column_widths[i]: column_widths[i] = width


s = ""
for record in records:
for column_width, field in zip(column_widths, record):
s += field.ljust(column_width+1)
s += "\n"


return s

我发现这个答案非常有帮助,而且非常优雅,最初来自 给你:

matrix = [["A", "B"], ["C", "D"]]


print('\n'.join(['\t'.join([str(cell) for cell in row]) for row in matrix]))

输出

A   B
C   D

下面是肖恩 · 钦的答案的一个变体。每列的宽度是固定的,而不是所有列的宽度。第一行下面和列之间也有一个边框。((咒语)库用于执行合同。)

@icontract.pre(
lambda table: not table or all(len(row) == len(table[0]) for row in table))
@icontract.post(lambda table, result: result == "" if not table else True)
@icontract.post(lambda result: not result.endswith("\n"))
def format_table(table: List[List[str]]) -> str:
"""
Format the table as equal-spaced columns.


:param table: rows of cells
:return: table as string
"""
cols = len(table[0])


col_widths = [max(len(row[i]) for row in table) for i in range(cols)]


lines = []  # type: List[str]
for i, row in enumerate(table):
parts = []  # type: List[str]


for cell, width in zip(row, col_widths):
parts.append(cell.ljust(width))


line = " | ".join(parts)
lines.append(line)


if i == 0:
border = []  # type: List[str]


for width in col_widths:
border.append("-" * width)


lines.append("-+-".join(border))


result = "\n".join(lines)


return result

这里有一个例子:

>>> table = [['column 0', 'another column 1'], ['00', '01'], ['10', '11']]
>>> result = packagery._format_table(table=table)
>>> print(result)
column 0 | another column 1
---------+-----------------
00       | 01
10       | 11

这是一个有点晚的聚会,一个无耻的插件为一个包我写的,但你也可以检查出 柱状包。

它接受一个输入列表和一个标题列表,并输出一个表格格式的字符串。下面的代码片段创建了一个 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)

Table Displaying No-border Style

或者你也可以用颜色和边框来装饰一下。 Table Displaying Spring Classics

要了解更多关于列大小调整算法的信息并查看 API 的其余部分,可以查看上面的链接或者查看 柱状 GitHub 回购

Scolp 是一个新的库,它允许您轻松地打印流式列数据,同时自动调整列宽。

(免责声明: 我是作者)

更新@Franck Dernoncourt 想象中的配方是 Python 3和 PEP8兼容

import io
import math
import operator
import re
import functools


from itertools import zip_longest




def indent(
rows,
has_header=False,
header_char="-",
delim=" | ",
justify="left",
separate_rows=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 row_wrapper(row):
new_rows = [wrapfunc(item).split("\n") for item in row]
return [[substr or "" for substr in item] for item in zip_longest(*new_rows)]


# break each logical row into one or more physical ones
logical_rows = [row_wrapper(row) for row in rows]
# columns of physical rows
columns = zip_longest(*functools.reduce(operator.add, logical_rows))
# get the maximum of each column by the string length of its items
max_widths = [max([len(str(item)) for item in column]) for column in columns]
row_separator = header_char * (
len(prefix) + len(postfix) + sum(max_widths) + len(delim) * (len(max_widths) - 1)
)
# select the appropriate justify method
justify = {"center": str.center, "right": str.rjust, "left": str.ljust}[
justify.lower()
]
output = io.StringIO()
if separate_rows:
print(output, row_separator)
for physicalRows in logical_rows:
for row in physicalRows:
print( output, prefix + delim.join(
[justify(str(item), width) for (item, width) in zip(row, max_widths)]
) + postfix)
if separate_rows or has_header:
print(output, row_separator)
has_header = 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 functools.reduce(
lambda line, word, i_width=width: "%s%s%s"
% (
line,
" \n"[
(
len(line[line.rfind("\n") + 1 :]) + len(word.split("\n", 1)[0])
>= i_width
)
],
word,
),
text.split(" "),
)




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




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 range(int(math.ceil(1.0 * 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, has_header=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,
has_header=True,
separate_rows=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 |
# ---------------------------------------------

这将根据其他答案中使用的 max 度量设置独立的、最适合的列宽度。

data = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']]
padding = 2
col_widths = [max(len(w) for w in [r[cn] for r in data]) + padding for cn in range(len(data[0]))]
format_string = "\{\{:{}}}\{\{:{}}}\{\{:{}}}".format(*col_widths)
for row in data:
print(format_string.format(*row))

为懒人准备的

使用 巨蟒3 * 熊猫/地熊猫; 通用的简单类内方法(对于“普通”脚本,只需删除 自我) :

函数着色:

    def colorize(self,s,color):
s = color+str(s)+"\033[0m"
return s

标题:

print('{0:<23} {1:>24} {2:>26} {3:>26} {4:>11} {5:>11}'.format('Road name','Classification','Function','Form of road','Length','Distance') )

然后是熊猫/地熊猫的数据框:

            for index, row in clipped.iterrows():
rdName      = self.colorize(row['name1'],"\033[32m")
rdClass     = self.colorize(row['roadClassification'],"\033[93m")
rdFunction  = self.colorize(row['roadFunction'],"\033[33m")
rdForm      = self.colorize(row['formOfWay'],"\033[94m")
rdLength    = self.colorize(row['length'],"\033[97m")
rdDistance  = self.colorize(row['distance'],"\033[96m")
print('{0:<30} {1:>35} {2:>35} {3:>35} {4:>20} {5:>20}'.format(rdName,rdClass,rdFunction,rdForm,rdLength,rdDistance) )

{0:<30} {1:>35} {2:>35} {3:>35} {4:>20} {5:>20}的涵义:

0, 1, 2, 3, 4, 5-> 列,在这种情况下总共有6个

列的宽度(注意,您必须添加 \033[96m的长度-这对 Python 来说也是一个字符串) ,只是实验:)

对齐: 右,左(还有用于填充零的 =)

如果你想要区分,例如最大值,你必须切换到特殊的熊猫风格的函数,但是假设这足以在终端窗口显示数据。

结果:

enter image description here

与之前的答案略有不同(我没有足够的名声来评论它)。格式库允许您指定元素的宽度和对齐方式,但不能指定元素的开始位置,即可以说“ be 20 column wide”,但不能说“ start in column 20”。这就引出了一个问题:

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


print("first row: {: >20} {: >20} {: >20}".format(*table_data[0]))
print("second row: {: >20} {: >20} {: >20}".format(*table_data[1]))
print("third row: {: >20} {: >20} {: >20}".format(*table_data[2]))

输出

first row:                    a                    b                    c
second row:           aaaaaaaaaa                    b                    c
third row:                    a           bbbbbbbbbb                    c

当然,答案也是格式化文字字符串,它与格式的结合有点奇怪:

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


print(f"{'first row:': <20} {table_data[0][0]: >20} {table_data[0][1]: >20} {table_data[0][2]: >20}")
print("{: <20} {: >20} {: >20} {: >20}".format(*['second row:', *table_data[1]]))
print("{: <20} {: >20} {: >20} {: >20}".format(*['third row:', *table_data[1]]))

输出

first row:                              a                    b                    c
second row:                    aaaaaaaaaa                    b                    c
third row:                     aaaaaaaaaa                    b                    c

您可以准备数据并将其传递给实际的 column实用程序。

假设您已经将数据打印到/tmp/filename.txt 文件,并使用选项卡作为分隔符。然后你可以这样写:

import subprocess


result = subprocess.run("cat /tmp/filename.txt | column -N \"col_1,col_2,col_3\" -t -s'\t' -R 2,3", shell=True, stdout=subprocess.PIPE)
print(result.stdout.decode("utf-8"))

如您所见,您可以使用列实用工具的特性,例如右对齐。

只有17个答案。巨蟒禅说: “应该有一种——最好只有一种——显而易见的方法来做到这一点。”

这里有第18种方法: 制表包支持一系列数据类型,它可以显示为表格,这里有一个简单的例子改编自它们的文档:

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
table = [['a', 'b', 'c'],
['aaaaaaaaaa', 'b', 'c'],
['a', 'bbbbbbbbbb', 'c']]




def print_table(table):
def get_fmt(table):
fmt = ''
for column, row in enumerate(table[0]):
fmt += '\{\{!s:<{}}} '.format(
max(len(str(row[column])) for row in table) + 2)
return fmt
fmt = get_fmt(table)
for row in table:
print(fmt.format(*row))




print_table(table)

格式化为表需要正确的填充。一个通用的解决方案是使用名为 prettytable的 python 包。虽然不依赖于库会更好,但是这个包处理所有的边界情况,并且简单,没有任何进一步的依赖性。

x = PrettyTable()
x.field_names =["field1", "field2", "field3"]
x.add_row(["col1_content", "col2_content", "col3_content"])
print(x)

基于其他一些答案,我认为我有一个相当可读和健壮的解决方案:

data = [['a', 'b', 'c'], ['aaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'ccc']]


padding = 4


# build a format string of max widths
# ex: '{:43}{:77}{:104}'
num_cols = len(data[0])
widths = [0] * num_cols
for row in data:
for i, value in enumerate(row):
widths[i] = max(widths[i], len(str(value)))


format_string = "".join([f'\{\{:{w+padding}}}' for w in widths])


# print the data
for row in data:
print(format_string.format(*[str(x) for x in row]))

这也通过在 str()中包装一些东西来支持记录中的 NoneType

这是一个有趣的小项目..。

Columns.py

from __future__ import annotations


from typing import TYPE_CHECKING


if TYPE_CHECKING:
from typing import Iterable, Iterator, Sequence, Sized


Matrix = Sequence[Sequence]




def all_elem_same_length(list: Sequence) -> bool:
length = len(list[0])


for elem in list:
if not len(elem) == length:
return False


return True




def get_col(matrix: Matrix, col_i: int) -> Iterator[Sized]:
return (row[col_i] for row in matrix)




def get_cols(matrix: Matrix) -> Iterator[Iterable[Sized]]:
return (get_col(matrix, col_i) for col_i in range(len(matrix[0])))




def get_longest_elem(list: Iterable[Sized]) -> Sized:
return max(list, key=len)




def get_longest_elem_per_column(matrix: Matrix) -> Iterator[Sized]:
return (get_longest_elem(col) for col in get_cols(matrix))




def get_word_pad_fstr(element: Sized, padding: int) -> str:
return f"\{\{:{len(element)+padding}}}"




def get_row_elem_pad_strings(matrix: Matrix, padding: int) -> Iterator[str]:
return (
get_word_pad_fstr(word, padding) for word in get_longest_elem_per_column(matrix)
)




def print_table(matrix: Matrix, padding=4) -> None:
if not all_elem_same_length(matrix):
raise ValueError("Table rows must all have the same length.")


format_string = "".join(get_row_elem_pad_strings(matrix, padding))


for row in matrix:
print(format_string.format(*(str(e) for e in row)))




if __name__ == "__main__":
data = [["a", "b", "c"], ["aaaaaaaa", "b", "c"], ["a", "bbbbbbbbbb", "ccc"]]


print_table(data)