私が作成したコマンドライン管理ツールで使用するために、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ライブラリを探しましたが、有用なものが見つかりません。
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()
を使用して各列を印刷するときに必要なパディングを追加します。
Python 2.6+以降では、次の方法で format string を使用して、列を最小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
これは2つのパスで行う必要があります。
str.ljust()
およびstr.rjust()
を使用して、最初のパスからの最大幅の知識を使用して列をフォーマットしますそのような列の転置は、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'
のような手の込んだテーブルを取得するには
---------------------------------------------------
| First Name | Last Name | Age | Position |
---------------------------------------------------
| John | Smith | 24 | Software |
| | | | Engineer |
---------------------------------------------------
| Mary | Brohowski | 23 | Sales |
| | | | Manager |
---------------------------------------------------
| Aristidis | Papageorgopoulos | 28 | Senior |
| | | | Reseacher |
---------------------------------------------------
これを使用できます Pythonレシピ :
'''
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 __== '__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
これはパーティーに少し遅れ、私が書いたパッケージの恥知らずなプラグインですが、 Columnar パッケージをチェックアウトすることもできます。
入力のリストのリストとヘッダーのリストを受け取り、テーブル形式の文字列を出力します。このスニペットは、ドッカー風のテーブルを作成します。
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 を参照してください。
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 __== "__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 |
# ---------------------------------------------
私はこの質問が古いことを知っていますが、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
この回答は、元々 here からの非常に有用でエレガントなものでした。
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 ライブラリは契約の実施に使用されます。)
@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