リスト内包表記再考
[expr for x in [...] for y in [...]]
は、
lst = [] for x in [...]: for y in [...]: lst.append(expr)
と同等ということらしい。
私の今までの研究の結果、リスト内包表記に関して重要なのは以下の3つ。
- 意味は異なるが、forを並列に並べるのとネストするのと2通りあるので、状況によって使い分けるべき
- リスト内包表記内では文が書けないので代入文が使えないが、一時変数を利用するテクニックがある
- リスト内包表記では、データの組から1つずつexprに変形したもののリストを作成するが、組を作るのとexprに変形するステップを2つに分けることができる
1つめは良いとして、まず2つめの方であるが、以下の方法で実現できる。
[expr for k in [値] for x in [...]]
上のコードのexpr内ではxの値の他にkの値を利用できる。kの値は1回のみ一時的な利用のために変数kに保存される。一時利用の変数を増やしたければ、
# 方法1 [expr for k, l in [(値1, 値2)] for x in [...]] # 方法2 [expr for k in [値1] for l in [値2] for x in [...]]
のようにいくらでも増やすことが可能。
参考:
次に3つめの例であるが、リスト内包では以下のように一気に結果のリストを作る。
>>> xs=[100,200] >>> ys=[1,2,3] >>> >>> [x+y ... for x in xs ... for y in ys] [101, 102, 103, 201, 202, 203]
しかし、これを以下のようにPython3で導入されたproductメソッドにより2つのステップに分解できる。
>>> from itertools import product >>> zs = product(xs, ys) # [(100, 1), (100, 2), (100, 3), (200, 1), (200, 2), (200, 3)] >>> [x+y for x,y in zs] [101, 102, 103, 201, 202, 203]
一気に作った方が良いのか、2つのステップに分けた方が良いのかはケースバイケースだと思うが、2つの選択肢があるということは意識すべき。Python3のitertoolsにはcombinations、combinations_with_replacement、compress、permutations、product、zip_longestなど色々面白いものが追加されている。
参考:
- n次元ループを1次元ループに変換する (3) (http://d.hatena.ne.jp/morchin/20070831#p1)
Python 3.1a1でテストしていて、上記のコードを
>>> zs = product(xs, ys) >>> map(lambda (x,y): x+y, zs)
のように書きたかったのだが、lambda (x,y): x+y が構文エラーになるというバグ?を見つけた。これはかなり不便。
追記(2009/4/1):
for x,y in product(xs, ys): do_process(x, y)
もしくは、
[do_process(x, y) for x,y in product(xs, ys)]
の書き方の方が、for文にせよリスト内包表記にせよ、forを2回使用した書き方より分かりやすい気がする。リスト内包表記と上記のコードとどちらが速いか計測していないが、もし上記のコードの方がパフォーマンス的に速ければ、2ステップに分けられるというよりも常用可能な定石的な書き方となりそう。
一瞬xsとysはイテレータだと2回以上取り出せないのでうまくいかないと心配したが、product()は、その辺もケアしてくれているみたい。ということは、パフォーマンス的には不利かな?時間があるときに調べてみるつもり。
Python3のformatメソッドの中途半端なところ
- Python 3.0 Hacks 第6回 Pythonicな文字列フォーマットforamat()メソッド (http://gihyo.jp/dev/serial/01/pythonhacks/0006)
formatメソッドで辞書の値を表示するには以下のように[]の中にキーの文字列を書く。
>>> d = {'foo': 'a'} >>> "{0[foo]}".format(d) 'a'
では、キーとなる文字列が変数に格納されている場合どうすれば良いか?以下のように、
>>> key='foo' >>> "{0[{1}]}".format(d, key) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: '{1}'
{}をネストすることはできないので、[]内の{1}がキーの文字列であると認識される。これは以下のように回避できる。
>>> "{0}".format(d[key]) 'a'
では次に辞書のキーが以下のように文字列でない場合はどうだろうか?
>>> d = {1: 'b', '1': 'c'} >>> "{0[1]}".format(d) 'b' >>> d = {1: 'b'} >>> "{0[1]}".format(d) 'b' >>> d = {'1': 'c'} >>> "{0[1]}".format(d) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 1 >>> >>> d = {3.0: 'd'} >>> "{0[3.0]}".format(d) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: '3.0' >>> d = {'3.0': 'd'} >>> "{0[3.0]}".format(d) 'd'
本来、[]内は文字列のキーが認識されるはずであるが、整数の1がキーとして認識されている。ところが、実数の場合は、文字列の方が認識されている。文字列型以外のキーを使用すると挙動が怪しいということが分かる。
結局、Rubyのように文字列の中に式を埋め込む方式であれば、"{d[1]}"や"{d['1'}}"のように曖昧さはなかったと思う。ただし、この方法だと右詰めや0埋めや有効桁数などの整形処理ができないし、{d[1]:10}のような組み合わせたやり方は恐らく構文解釈が複雑なので、今の方式で辞書を使用する場合には注意が必要ということを覚えておくしかない。
ちなみに、属性に関しては以下のように文字列が使われるので問題なさそう。
>>> def foo():pass ... >>> foo.__dict__ = {1: 'a', '1': 'b'} >>> "{0.1}".format(foo) 'b' >>> foo.__dict__ = {1.0: 'a', '1.0': 'b'} >>> "{0.1.0}".format(foo) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'function' object has no attribute '1' >>> foo.__dict__ = {True: 'a', 'True': 'b'} >>> "{0.True}".format(foo) 'b' >>> foo.__dict__ = {None: 'a', 'None': 'b'} >>> "{0.None}".format(foo) 'b'