こちらはPythonのリスト内包表記が100倍楽しくなる記事です。
さて、Pythonを使っていてリスト内包表記が出てくると初めはとても戸惑うかもしれません。
しかし、なんら臆することはありません。よく見てみたらそれはただの見慣れたネスト文なのです。
リスト内包表記とは
リスト内包表記とは、for
文やif
を使ってある条件のリストのみを取りだしたい場合に使える記述方法のことです。
たとえば、偶数のみの配列をつくりたい場合、人々はズル賢いのでNumpy
のarange()
メソッドを思い浮かぶかもしれませんがこれだとlist
型を受け取ることができません。
これを純粋なPythonのみで記述しようとすると一気に知能レベルが低下し
even_numbers = []
for i in range(10):
if i % 2 == 0:
even_numbers.append(i)
とするでしょう。しかし、人間は学習能力を持ちます。もしかしたらPython特有のリスト内包表記の存在に気づくかもしれません。
even_numbers = [number for number in range(10) if number % 2 == 0]
と一行で記述することができます。
Swiftとかにも三項演算子なんかがありますが、Pythonのすごいところはここから。
たとえば、こんな二重for
文は
years = [2016, 2017, 2018, 2019]
months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
calendar = []
for year in years:
for month in months:
calendar.append(f'{year}年{month}月')
リスト内包表記だと
calendar = [f'{year}年{month}月' for year in years for month in months]
こんな感じで一行で書けます。
単にappend()
を先に書くか後に書くかの違いですがこれは動的な型付け言語だからこそ成り立っています。
リスト内包表記のアルゴリズム
それでは本題に入っていきます。
まず、こんなデータを想定します。
説明のためシンプルにしましたが本来は同一のkey
が複数あることを想像しておいてください。
これをjson形式に表すとこんな感じです
users = {1: {1: "jiro", 2: "goro"}, 2: "aya"}
そして、たとえば、"goro"
にあたるvalue
を取り出す場合、その取りだしたい"goro"
が複数あることを想定すると以下のようにfor
文で記述しますね。
user = []
for a, b in users.items():
if a == 1:
for c, d in b.items():
if c == 2:
user.append(d)
一文字変数なんで読みづらいですが抽象化のためなので悪しからず。
こうすると、人々の認識通り、上のfor
から順番に下に降りてきて"goro"
に辿りつきます。
これをリスト内包表記で記述したい場合、
user = [d for a, b in users.items() if a == 1 for c, d in b.items() if c == 2]
となります。本当にappend()
が後か先かだけです。
他の記事で
外側の
for
文を後ろに書く
という表記をよく見かけますが、これは危険です。
なぜなら結局リスト内包表記もfor
文と同じで記述した順に探索していくからです。
特に今回のようなループしたい変数が上から下に降りてくるようなネストでは記述の順番を変えてしまうと定義前となってしまう可能性が高いです。
これをリスト内包表記で書くと
user = [d for c, d in b.items() if c == 2 for a, b in users.items() if a == 1]
当たり前ですがこういったエラーが発生します
'str' object has no attribute 'items'
この場合、データの深度がバラバラなためまずまず探索ができませんというエラーを返します。
しかし、このように深度を揃えたとしても
users = {1: {1: "jiro", 2: "goro"}, 2: {1: "aya"}}
深度を遡って探索することになり、未定義エラーが発生します。
b is not defined
なので、公式ドキュメントにも記載してある通り、リスト内包表記は通常のfor文と同じ順番でappend()
のみを先頭に持ってくる感じで書きましょう。
さしづめ後置for
文といったところでしょう。
まとめ
つまり、リスト内包表記に通常のfor
文とのアルゴリズムの差異はありません。
リスト内包表記が高速だと言われているのは初めから変数に内包しているためと、そうすることでappend()
を呼び出す必要がなくなるからでしょう。