リスト内包表記再考

[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など色々面白いものが追加されている。

参考:


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()は、その辺もケアしてくれているみたい。ということは、パフォーマンス的には不利かな?時間があるときに調べてみるつもり。