Python で、
+--------------+----------+----------+ | Header 1 | Header 2 | Header 3 | +--------------+----------+----------+ | aaa | bbb | ccc | | aaaaaaaaaaaa | bb | ccccc | | a | b | | +--------------+----------+----------+
こんな感じのテーブル表記をするなら、「PrettyTable」を使うのが一般的でしょうか。
PrettyTable
(GitHubには本家のコードはないようです。誰かが作ったクローンなら これ とか)
有名なプロダクトでも普通に使われているようです。
例えば、OpenStack の コマンドクライアントでは、コマンドの結果をテーブル表記で出力するのに使っていますね。
https://github.com/openstack/python-novaclient/blob/master/novaclient/utils.py
しかしながら、標準で入っていないことが多いので、
$ sudo pip install prettytable $ pip list | grep prettytable prettytable (0.7.2) $ python Python 2.7.5 (default, Mar 9 2014, 22:15:05) >>> import prettytable
と、インストールが必要なのが若干難点です。
本番環境とかでちょっとしたコマンドを作りたいときなど、余計なソフトをインストールしたくないケースでは、PrettyTable が候補にならない場合がありますよね(少なくとも私の場合は)。
ということで、家で(!)せっせか自作してみました。
simple_table.py
# -*- coding: utf-8 -*- from itertools import izip_longest class SimpleTable(object): """ SimpleTable """ def __init__(self, header=None, rows=None): self.header = header or () self.rows = rows or [] def set_header(self, header): self.header = header def add_row(self, row): self.rows.append(row) def _calc_maxes(self): array = [self.header] + self.rows return [max(len(str(s)) for s in ss) for ss in izip_longest(*array, fillvalue='')] def _get_printable_row(self, row): maxes = self._calc_maxes() return '| ' + ' | '.join([('{0: <%d}' % m).format(r) for r, m in izip_longest(row, maxes, fillvalue='')]) + ' |' def _get_printable_header(self): return self._get_printable_row(self.header) def _get_printable_border(self): maxes = self._calc_maxes() return '+-' + '-+-'.join(['-' * m for m in maxes]) + '-+' def get_table(self): lines = [] if self.header: lines.append(self._get_printable_border()) lines.append(self._get_printable_header()) lines.append(self._get_printable_border()) for row in self.rows: lines.append(self._get_printable_row(row)) lines.append(self._get_printable_border()) return lines def print_table(self): lines = self.get_table() for line in lines: print(line)
GitHub にもアップしておきました。
https://github.com/akiyoko/python-simple-table/blob/master/simple_table.py
SimpleTable の使い方
使い方はこんな感じです。
>>> from simple_table import SimpleTable >>> >>> table = SimpleTable() >>> table.set_header(('Header 1', 'Header 2', 'Header 3')) >>> table.add_row(('aaa', 'bbb', 'ccc')) >>> table.add_row(('aaaaaaaaaaaa', 'bb', 'ccccc')) >>> table.add_row(('a', 'b')) >>> table.print_table() +--------------+----------+----------+ | Header 1 | Header 2 | Header 3 | +--------------+----------+----------+ | aaa | bbb | ccc | | aaaaaaaaaaaa | bb | ccccc | | a | b | | +--------------+----------+----------+
あまりないかもしれませんが、こういう使い方もできます。
>>> table = SimpleTable(('Header 1', 'Header 2', 'Header 3'), [('aaa', 'bbb', 'ccc'), ('aaaaaaaaaaaa', 'bb', 'ccccc'), ('a', 'b')]) >>> table.print_table() +--------------+----------+----------+ | Header 1 | Header 2 | Header 3 | +--------------+----------+----------+ | aaa | bbb | ccc | | aaaaaaaaaaaa | bb | ccccc | | a | b | | +--------------+----------+----------+ >>> table = SimpleTable(None, [('aaa', 'bbb', 'ccc'), ('aaaaaaaaaaaa', 'bb', 'ccccc'), ('a', 'b')]) >>> table.print_table() +--------------+-----+-------+ | aaa | bbb | ccc | | aaaaaaaaaaaa | bb | ccccc | | a | b | | +--------------+-----+-------+
事前準備
インタプリタで実行する場合は、事前に simple_table.py にパスを通しておく必要があります。
いろいろやり方がありますが、いくつか方法を挙げておきます。
(方法その1)
Mac における Pythonの標準ライブラリ「/Library/Python/2.7/site-packages/」に pyファイルを直接置くのが一番簡単かもしれません。
$ sudo cp /Users/akiyoko/github/python-simple-table/simple_table.py /Library/Python/2.7/site-packages/
(方法その2)
あるいは、Python のインタープリタを実行するカレントディレクトリに置くか、
$ cp /Users/akiyoko/github/python-simple-table/simple_table.py . $ python Python 2.7.5 (default, Mar 9 2014, 22:15:05) >>> import simple_table
ちょっと解説
1) 各カラムの最大サイズの取り方
これを参考にしつつ、ちょっと工夫してみました。
http://stackoverflow.com/questions/6018916/find-max-length-of-each-column-in-a-list-of-lists
>>> from itertools import izip_longest >>> array = [(1, 'This is a test', 12039), (12, 'test', 1235)] >>> [max(len(str(s)) for s in ss) for ss in izip_longest(*array, fillvalue='')] [2, 14, 5]
zip を使わなかったのは、カラムの数が違う場合にカラム数が短い方に合わせて縮小させられてしまうからです。
>>> array = [(1, 'This is a test', 12039), (12, 'test')] >>> [max(len(str(s)) for s in ss) for ss in zip(*array)] [2, 14] # これを [2, 14, 5] となるようにしたい
2) zip と map、そして izip_longest
複数のリスト(やタプル)から同じインデックスにある要素をセットにする場合、zip や map を使いますが、zip は短い方、map は長い方に合わせてくれます。
参考
map の第一引数には関数を指定できるのですが、zip と同じ使い方をしたいなら「None」でよいでしょう。
それでも若干違いがあるのですが、今回 map を使わなかったのはその違いが影響したためでした。結局、izip_longest というのを使っています。
3) 高度な文字列フォーマット
format() を使って、柔軟な文字列フォーマットができます。「<」「>」「^」のメタ記号で、左寄せ・右寄せ・センタリングが指定できます。
便利ですね。
>>> '{0: <10}'.format('12345') '12345 ' >>> '{0: >10}'.format('12345') ' 12345' >>> '{0: ^10}'.format('12345') ' 12345 '
Python 組み込み関数の ljust(), rjust(), center() を使っても同じ結果が得られます。
>>> '12345'.ljust(10) '12345 ' >>> '12345'.rjust(10) ' 12345' >>> '12345'.center(10) ' 12345 '
参考
- http://python.civic-apps.com/string-format-function/
- http://docs.python.jp/2/library/string.html#formatstrings
日本語にも対応させました (※2014/6/25 追記)
日本語(2バイト文字)に対応していなかったので、対応させました。
len() だけで横幅を計算しようとしていたのがダメだったようでした。
len() は str型 と unicode型 で微妙に利用目的?が違うらしく
str: バイト数
unicode: 文字数
なので、それぞれちゃんと使い分けてください、私。
「Python で文字数を数えるときの注意点 - みひゃろぐ」より
そこで、
- http://mogproject.blogspot.jp/2013/02/python-how-to-get-width-of-east-asian.html
- http://0xcc.net/blog/archives/000191.html
- http://d.hatena.ne.jp/hush_puppy/20090227/1235740342
あたりを参考にさせていただき、日本語(2バイト文字)を2文字としてカウントするように修正しました。
具体的にはこんな感じ。
def _unicode_width(self, s, width={'F': 2, 'H': 1, 'W': 2, 'Na': 1, 'A': 2, 'N': 1}): s = unicode(s) return sum(width[east_asian_width(c)] for c in s)
詳しくは、https://github.com/akiyoko/python-simple-table/blob/master/simple_table.py
おかげで、こんな勝敗表を作ることができましたー。
>>> from simple_table import SimpleTable >>> table = SimpleTable() >>> table.set_header(('', u'勝', u'分', u'敗', u'勝点', u'得失差')) >>> table.add_row((u'コロンビア', 3, 0, 0, 9, u'+7')) >>> table.add_row((u'ギリシャ', 1, 1, 1, 4, u'-2')) >>> table.add_row((u'コートジボワール', 1, 0, 2, 3, u'-1')) >>> table.add_row((u'日本', 0, 1, 2, 1, u'-4')) >>> table.print_table() +------------------+----+----+----+------+--------+ | | 勝 | 分 | 敗 | 勝点 | 得失差 | +------------------+----+----+----+------+--------+ | コロンビア | 3 | 0 | 0 | 9 | +7 | | ギリシャ | 1 | 1 | 1 | 4 | -2 | | コートジボワール | 1 | 0 | 2 | 3 | -1 | | 日本 | 0 | 1 | 2 | 1 | -4 | +------------------+----+----+----+------+--------+
(はてなブログで書くとテーブルがずれてしまいますね。。まあ、しゃーない。)
PrettyTable の使い方
最後に、PrettyTable の使い方を紹介。
チュートリアルそのままですが。
https://code.google.com/p/prettytable/wiki/Tutorial
>>> from prettytable import PrettyTable >>> x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"]) >>> x.add_row(["Adelaide",1295, 1158259, 600.5]) >>> x.add_row(["Brisbane",5905, 1857594, 1146.4]) >>> x.add_row(["Darwin", 112, 120900, 1714.7]) >>> x.add_row(["Hobart", 1357, 205556, 619.5]) >>> x.add_row(["Sydney", 2058, 4336374, 1214.8]) >>> x.add_row(["Melbourne", 1566, 3806092, 646.9]) >>> x.add_row(["Perth", 5386, 1554769, 869.4]) >>> print x.get_string() +-----------+------+------------+-----------------+ | City name | Area | Population | Annual Rainfall | +-----------+------+------------+-----------------+ | Adelaide | 1295 | 1158259 | 600.5 | | Brisbane | 5905 | 1857594 | 1146.4 | | Darwin | 112 | 120900 | 1714.7 | | Hobart | 1357 | 205556 | 619.5 | | Sydney | 2058 | 4336374 | 1214.8 | | Melbourne | 1566 | 3806092 | 646.9 | | Perth | 5386 | 1554769 | 869.4 | +-----------+------+------------+-----------------+ >>> print x.get_html_string() <table> <tr> <th>City name</th> <th>Area</th> <th>Population</th> <th>Annual Rainfall</th> </tr> <tr> <td>Adelaide</td> <td>1295</td> <td>1158259</td> <td>600.5</td> </tr> ・ ・
参考: