1行に1つのアイデアを
もう何度も書いてきたパターンですよね。空のリストを用意して、何かをループで回し、条件で絞り込んで、結果にappendしていく。
リスト内包表記を使えば、これを1行で書けます。
左から順に読んでみましょう。「numbers の各要素 n について、n * 2 を並べた新しいリスト」となります。基本の形は [式 for 要素 in イテラブル] です。
これは Python 特有のテクニックというわけではなく、一般に 内包表記(comprehension) と呼ばれるものです。リストの作り方を手続き的に書き下すのではなく、「何が入るか」を宣言的に記述する書き方、というのがポイントです。
if で絞り込む
ループ部分の後ろに if を付けると、条件でフィルタできます。
2つ目は「numbers の各 n について n * n、ただし n が奇数のときだけ」と読みましょう。
同じ処理をループで書くと次のようになります。
どちらでも問題ありません。内包表記の方が短くて済みますし、慣れてくると「何をしているか」が1行にまとまっているので、かえって読みやすく感じるはずです。
map と filter を内包表記でまとめる
値を関数に通しつつ、同時に条件で絞り込むこともできます。
3文字より長い単語だけ、大文字版も一緒に含めます。
内包表記でのネストしたループ
for を2つ並べると、直積(デカルト積)のような組み合わせが作れます。
順序はループをネストした場合と同じで、最初の for が外側、2 つ目が内側になります。インデントした通常のループと同じく、前から順に読んでいける形です。
ネストは 2 段までが実用的な限界で、それ以上深くなるなら普通のループで書いたほうが読みやすくなります。3 段になったら迷わずループに戻しましょう。
辞書内包表記と集合内包表記
考え方はリスト内包表記と同じで、括弧の種類が変わるだけです。
集合内包表記はパッと見、辞書内包表記とほぼ同じに見えます。違いは key: value の形か、式がひとつだけかという点。中括弧に : があれば辞書、: がなければ集合、と覚えておけばOKです。
ジェネレータ式
リスト内包表記とほぼ瓜二つですが、角括弧ではなく丸括弧を使います。そして何より大事なのは、リストを作らないという点です。
ジェネレータを sum() や any() にそのまま渡している点に注目してください。余計な括弧は不要です。ジェネレータ式は、呼び出し側が一度だけイテレートできればいい場面にぴったりの道具です。大きなコレクションを扱うときは、リストを丸ごと作るよりメモリに優しくなります。
内包表記ではなく普通のループを使うべきとき
内包表記は魅力的で、その気になれば1行にいくらでも詰め込めます。でも、詰め込むべきではありません。
次のような場合は、素直に for ループで書きましょう。
- 変換処理が2段階以上にわたるとき。途中の変数が必要なら、ループとして展開したほうが読みやすくなります。
- 複雑なエラーハンドリングや分岐が絡むとき。
- その1行を読む人が、理解するのに何度も止まって考え込むようなとき。
私がいつも使っている基準は、「一息で読めて意味がすぐ分かる内包表記ならOK、つっかえたらループに書き直す」というものです。
一見賢そうでも、かえって可読性を損なう内包表記もあります。
結果は同じです。ループ版は1行ではなく5行になりますが、「長い=悪い」というわけではありません。
よく使うパターンをいくつか
この 5 つのパターンを押さえておくだけで、日常的なデータ処理のかなりの部分はカバーできてしまいます。
次のステップ
ここまでで、主要なコレクション型と、それらを結びつける内包表記について一通り見てきました。次の章では関数を取り上げます。処理をひとつの名前にまとめて、何度でも再利用できる形にパッケージングしていきましょう。
よくある質問
リスト内包表記とは何ですか?
既存のイテラブルから新しいリストを作るための、コンパクトな書き方です。たとえば [x * 2 for x in numbers] と書けば、各要素を2倍にした新しいリストができます。[x for x in numbers if x > 0] のように if を付ければフィルタもまとめて書けます。
リスト内包表記を使わない方がいいのはどんなとき?
読みにくくなるときです。内側の式が複雑だったり、ネストが深くなってきたら、普通の for ループに変数名を付けて書いた方がずっと分かりやすくなります。内包表記はあくまで シンプルな 変換・抽出のためのもの、と割り切るのがコツです。
リスト内包表記とジェネレータ式の違いは?
リスト内包表記はリスト全体をメモリに作ります。一方、ジェネレータ式(書き方はほぼ同じで、括弧が () になる)は要素を1つずつ生成します。sum(...) のように一度舐めるだけで捨てるような処理に渡すなら、ジェネレータ式の方が無駄がありません。