当サイトは、アフィリエイト広告を利用しています
pythonでリストから特定の値を取得する際などに
使うジェネレータ式の使い方を調べたので忘備録として残す。
当記事では
について記載する。
ジェネレータ式はpythonで効率的にデータを処理する方法の一つで
遅延評価を利用して要素を一つずつ生成する方法のこと。
具体的にはリストなどの反復可能オブジェクト(iterable)から
ジェネレータオブジェクトを生成する
ジェネレータオブジェクトはジェネレータ式を実行した結果生成される
特殊なオブジェクトのこと。
ジェネレータオブジェクトは遅延評価の仕組みを採用しており
値が必要になるまで計算はしない。
そのため、データを使用する場合はジェネレータオブジェクトから
値を取り出す必要がある。
# 反復可能オブジェクト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}]# terget_userはジェネレータオブジェクトterget_user = (user for user in users if user["user_id"] == "1")
terget_userhはジェネレータオブジェクトであるため
使うには値を取り出す必要がある
※このままでは何もできない
ジェネレータオブジェクトの仕組みである遅延評価は
作成時に即座に値を計算するのではなく、必要な時に評価して使う仕組みのこと。
評価方法としては主に
がよく使われる
※詳しくは後述する
ジェネレータ式は上記の通り、遅延評価のジェネレータオブジェクトを生成するので
必要な時まで計算を行わない。つまり、評価するまではメモリを消費しない。
そのため
の利点がある。
ジェネレータ式の具体的な使い方をまとめる
ジェネレータ式の構文は
(式 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()で前の値に戻ることはできないということ。
また一度使い切ったジェネレータは再利用できない。
使いどころとしてはリストなどの反復可能なオブジェクトなオブジェクトから
指定した条件のデータを取得する等のリスト内包表記と同じような使い方ができる
使い方としてリスト内包表記と同じなので、処理対象のデータが大量データであったり
またメモリを効率良く使う必要がある等の対象データで判断する。
いくつかパターンを実装して確認する
ジェネレータ式を使ってリストの中からデータを1件抽出して
各種の遅延評価を行う
# 辞書リスト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}]# ジェネレータ式で1件を抽出terget_user = (user for user in users if user["user_id"] == "1")# 遅延評価(next)terget_user_val = next(terget_user)print(terget_user_val)# 実行結果# {'user_id': '1', 'name': 'Tujimura', 'age': 11}
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}]# ジェネレータ式で1件を抽出terget_user = (user for user in users if user["user_id"] == "1")# 遅延評価(next)terget_user_val = list(terget_user)print(terget_user_val)# 実行結果# [{'user_id': '1', 'name': 'Tujimura', 'age': 11}]
listを使うと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}]# ジェネレータ式で1件を抽出terget_user = (user for user in users if user["user_id"] == "1")# 遅延評価(for)for user in terget_user:print(user)# 実行結果# {'user_id': '1', 'name': 'Tujimura', 'age': 11}
for文内では遅延評価が実行される
from pydantic import BaseModel# pydanticデータモデルclass User(BaseModel):user_id: intname: strage: int# ユーザーデータ(User インスタンスのリスト)users = [User(user_id=1, name="Tujimura", age=11),User(user_id=2, name="mori", age=20),User(user_id=3, name="shimada", age=50),User(user_id=4, name="kyogoku", age=70),]# ジェネレータ式で1件を抽出と同時にnextで遅延評価terget_user = next((user for user in usersif user.user_id == 1),None)print(terget_user.model_dump())# 実行結果# {'user_id': '1', 'name': 'Tujimura', 'age': 11}
from pydantic import BaseModel# pydanticデータモデルclass User(BaseModel):user_id: intname: strage: int# ユーザーデータ(User インスタンスのリスト)users = [User(user_id=1, name="Tujimura", age=11),User(user_id=2, name="mori", age=20),User(user_id=3, name="shimada", age=50),User(user_id=4, name="kyogoku", age=70),]# ジェネレータ式で1件を抽出と同時にlistで遅延評価terget_user = list((user for user in usersif user.user_id == 1))print(terget_user)# 実行結果# [User(user_id=1, name='Tujimura', age=11)]
listで取得できる
from pydantic import BaseModel# pydanticデータモデルclass User(BaseModel):user_id: intname: strage: int# ユーザーデータ(User インスタンスのリスト)users = [User(user_id=1, name="Tujimura", age=11),User(user_id=2, name="mori", age=20),User(user_id=3, name="shimada", age=50),User(user_id=4, name="kyogoku", age=70),]# ジェネレータ式で1件を抽出terget_user = ((user for user in usersif user.user_id == 1))# forで遅延評価for user in terget_user:print(user)# 実行結果# user_id=1 name='Tujimura' age=11
ジェネレータ式を使ってリストの中からデータを複数抽出して
各種の遅延評価を行う
# 辞書リスト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}]# ジェネレータ式で抽出target_users = (user for user in users if user["age"] > 20)# next() を取得件数分呼び出すwhile True:# 遅延評価user = next(target_users, None) # 次の要素を取得if user is None: # 取得できなくなったら終了breakprint(user)# 実行結果# {'user_id': '3', 'name': 'shimada', 'age': 50}# {'user_id': '4', 'name': 'kyogoku', 'age': 70}
取得件数分、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}]# ジェネレータ式を list() で評価target_users_list = list(user for user in users if user["age"] > 20)# 結果を出力(遅延評価)print(target_users_list)# 実行結果# [# {'user_id': '3', 'name': 'shimada', 'age': 50},# {'user_id': '4', 'name': 'kyogoku', 'age': 70}# ]
リストの場合はすべての要素をリスト化して使い回せるが
大量データの場合はメモリを圧迫する可能性がある
# 辞書リスト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}]# ジェネレータ式で条件に合うユーザーを抽出target_users = (user for user in users if user["age"] > 20)# `for` ループで遅延評価しながら1件ずつ処理for user in target_users:print(user)# 実行結果# {'user_id': '3', 'name': 'shimada', 'age': 50}# {'user_id': '4', 'name': 'kyogoku', 'age': 70}
from pydantic import BaseModel# pydanticデータモデルclass User(BaseModel):user_id: intname: strage: int# ユーザーデータ(User インスタンスのリスト)users = [User(user_id=1, name="Tujimura", age=11),User(user_id=2, name="mori", age=20),User(user_id=3, name="shimada", age=50),User(user_id=4, name="kyogoku", age=70),]# ジェネレータ式で条件に合うユーザーを抽出target_users = (user for user in users if user.age > 20)# next() を取得件数分呼び出すwhile True:# 遅延評価user = next(target_users, None) # 次の要素を取得if user is None: # 取得できなくなったら終了breakprint(user)# 実行結果# {'user_id': '3', 'name': 'shimada', 'age': 50}# {'user_id': '4', 'name': 'kyogoku', 'age': 70}
from pydantic import BaseModel# pydanticデータモデルclass User(BaseModel):user_id: intname: strage: int# ユーザーデータ(User インスタンスのリスト)users = [User(user_id=1, name="Tujimura", age=11),User(user_id=2, name="mori", age=20),User(user_id=3, name="shimada", age=50),User(user_id=4, name="kyogoku", age=70),]# ジェネレータ式を list() で評価し、条件に合うユーザーを抽出target_users_list = list(user for user in users if user.age > 20)# 結果を出力(遅延評価はなくなり全件リストに格納されている)print(target_users_list)# 実行結果# [user_id=3 name='shimada' age=50, user_id=4 name='kyogoku' age=70]
from pydantic import BaseModel# pydanticデータモデルclass User(BaseModel):user_id: intname: strage: int# ユーザーデータ(User インスタンスのリスト)users = [User(user_id=1, name="Tujimura", age=11),User(user_id=2, name="mori", age=20),User(user_id=3, name="shimada", age=50),User(user_id=4, name="kyogoku", age=70),]# ジェネレータ式で条件に合うユーザーを抽出target_users = (user for user in users if user.age > 20)# `for` ループで遅延評価しながら1件ずつ処理for user in target_users:print(user)# 実行結果# user_id=3 name='shimada' age=50# user_id=4 name='kyogoku' age=70
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() を使ってすべての要素を一気に取り出す
となくなる。
データサイズが小さいや再利用したい場合に使う。
※その場合はリスト内表表記を使ったほうがいい。
最後にリスト内包表記との違いをまとめておく。
リスト内包表記は
[式 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のジェネレータ式の使い方をまとめてみた。
処理するデータによってうまくリスト内包表記と使い分ける
必要がありそう。