当サイトは、アフィリエイト広告を利用しています
pythonでリストから特定の値を取得する際などに
使うジェネレータ式の使い方を調べたので忘備録として残す。
当記事では
について記載する。
ジェネレータ式はpythonで効率的にデータを処理する方法の一つで
遅延評価を利用して要素を一つずつ生成する方法のこと。
具体的にはリストなどの反復可能オブジェクト(iterable)から
ジェネレータオブジェクトを生成する
ジェネレータオブジェクトはジェネレータ式を実行した結果生成される
特殊なオブジェクトのこと。
ジェネレータオブジェクトは遅延評価の仕組みを採用しており
値が必要になるまで計算はしない。
そのため、データを使用する場合はジェネレータオブジェクトから
値を取り出す必要がある。
ジェネレータオブジェクトの仕組みである遅延評価は
作成時に即座に値を計算するのではなく、必要な時に評価して使う仕組みのこと。
評価はnextやfor文を使って行う。
ジェネレータ式は上記の通り、遅延評価のジェネレータオブジェクトを生成するので
必要な時まで計算を行わない。つまり、評価するまではメモリを消費しない。
そのため
の利点がある。
ジェネレータ式の具体的な使い方をまとめる
ジェネレータ式の構文は
(式 for 変数 in iterable if 条件)
のようになる
※()以外はリスト内包表記と同じ。
# ディクショナリリストusers = [{"user_id": "1", "name": "Tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},{"user_id": "3", "name": "shimada", "age": 50},{"user_id": "4", "name": "kyogoku", "age": 70}]# ジェネレータ式でジェネレータオブジェクト生成user_names = (user["name"] for user in users)
ディクショナリリストから「name」だけのジェネレータオブジェクトを生成
この段階ではuser_namesはまだなにも計算していない。
# ディクショナリリストusers = [{"user_id": "1", "name": "Tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},{"user_id": "3", "name": "shimada", "age": 50},{"user_id": "4", "name": "kyogoku", "age": 70}]# ジェネレータ式でジェネレータオブジェクト生成user_names = (user["name"] for user in users)# forで遅延評価して値を取り出すfor name in user_names:print(name) # ['Tujimura', 'mori', 'shimada', 'kyogoku']
for文を使って値を評価してprintで出力している。
next()を使う場合は下記のようになる。
# ディクショナリリストusers = [{"user_id": "1", "name": "Tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},{"user_id": "3", "name": "shimada", "age": 50},{"user_id": "4", "name": "kyogoku", "age": 70}]# ジェネレータ式でジェネレータオブジェクト生成user_names = (user["name"] for user in users)# nextで遅延評価して値を取り出すprint(next(user_names)) # Tujimuraprint(next(user_names)) # moriprint(next(user_names)) # shimadaprint(next(user_names)) # kyogokuprint(next(user_names)) # StopIteration
nextを使って順次、ジェネレータオブジェクトを遅延評価して取り出す。
値がなくなるとStopIterationとなる。
ジェネレータオブジェクトを注意点として
というのがあるので注意。
# nextで遅延評価して値を取り出すprint(next(user_names)) # Tujimuraprint(next(user_names)) # moriprint(next(user_names)) # shimadaprint(next(user_names)) # kyogokuprint(next(user_names)) # StopIteration
でいうと、next()で前の値に戻ることはできないということ。
また一度使い切ったジェネレータは再利用できない。
使いどころとしてはリストなどの反復可能なオブジェクトなオブジェクトから
指定した条件のデータを取得する等のリスト内包表記と同じような使い方ができる
使い方としてリスト内包表記と同じなので、処理対象のデータが大量データであったり
またメモリを効率良く使う必要がある等の対象データで判断する。
いくつかパターンを実装して確認する
users = [{"user_id": "1", "name": "Tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},{"user_id": "3", "name": "shimada", "age": 50},{"user_id": "4", "name": "kyogoku", "age": 70}]user_names = (user["name"] for user in users)print(list(user_names)) # ['Tujimura', 'mori', 'shimada', 'kyogoku']
ただし、ジェネレータ式の利点(特に 遅延評価 と メモリ効率)は、list() を使ってすべての要素を一気に取り出す
となくなる。
データサイズが小さいや再利用したい場合に使う。
※その場合はリスト内表表記を使ったほうがいい。
users = [{"user_id": "1", "name": "Tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},{"user_id": "3", "name": "shimada", "age": 50},{"user_id": "4", "name": "kyogoku", "age": 70}]adult_users = (user for user in users if user["age"] <= 20)print(next(adult_users)) # {'user_id': '1', 'name': 'Tujimura', 'age': 11}print(next(adult_users)["name"]) # moriprint(next(adult_users,None)) # None
最後にリスト内包表記との違いをまとめておく。
リスト内包表記は
[式 for 変数 in iterable if 条件]
のように[]で囲むのに対して、ジェネレータ式は
(式 for 変数 in iterable if 条件)
()で囲む
書くときはいいが、読むときは以外と間違える。。。
リスト内包表記は結果を新しいリストで返却する
users = [{"user_id": "1", "name": "Tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},{"user_id": "3", "name": "shimada", "age": 50},{"user_id": "4", "name": "kyogoku", "age": 70}]user_names = [user["name"] for user in users]print(user_names) # ['Tujimura', 'mori', 'shimada', 'kyogoku']
リストで帰ってくるため、特になにもしなくても、そのまま処理ができる
対してジェネレータ式はジェネレータオブジェクトで帰ってくるため
評価しないと処理ができない
# ディクショナリリストusers = [{"user_id": "1", "name": "Tujimura", "age": 11},{"user_id": "2", "name": "mori", "age": 20},{"user_id": "3", "name": "shimada", "age": 50},{"user_id": "4", "name": "kyogoku", "age": 70}]# ジェネレータ式でジェネレータオブジェクト生成user_names = (user["name"] for user in users)# listで取り出しprint(list(user_names)) # ['Tujimura', 'mori', 'shimada', 'kyogoku']
メモリ上に新しいリストを即座にメモリ上に展開される。
※大量データの場合は多くのメモリを使う
遅延評価なのでforやnextを使うまではメモリを消費しない。
必要なときに1つずつ生成され、処理が終わればその要素は破棄される。
リスト内包表記について下記記事でまとめている
またリストのデータ処理に関してはmapやfilterなどを使う手もある
pythonのジェネレータ式の使い方をまとめてみた。
処理するデータによってうまくリスト内包表記と使い分ける
必要がありそう。