当サイトは、アフィリエイト広告を利用しています

【Python】データを効率的に処理するジェネレータ式の使い方

作成日:2024月12月02日
更新日:2024年12月02日

pythonでリストから特定の値を取得する際などに
使うジェネレータ式の使い方を調べたので忘備録として残す。

当記事では

  • ジェネレータ式とは?
  • ジェネレータ式の使い方
  • ジェネレータ式の使いどころ
  • リスト内包表記との違い

について記載する。

ジェネレータ式とは?

ジェネレータ式はpythonで効率的にデータを処理する方法の一つで
遅延評価を利用して要素を一つずつ生成する方法のこと。

具体的にはリストなどの反復可能オブジェクト(iterable)から
ジェネレータオブジェクトを生成する

ジェネレータオブジェクトとは?

ジェネレータオブジェクトはジェネレータ式を実行した結果生成される
特殊なオブジェクトのこと。

ジェネレータオブジェクトは遅延評価の仕組みを採用しており
値が必要になるまで計算はしない。
そのため、データを使用する場合はジェネレータオブジェクトから
値を取り出す必要がある。

遅延評価とは?

ジェネレータオブジェクトの仕組みである遅延評価は
作成時に即座に値を計算するのではなく、必要な時に評価して使う仕組みのこと。
評価はnextやfor文を使って行う。

ジェネレータ式の利点

ジェネレータ式は上記の通り、遅延評価のジェネレータオブジェクトを生成するので
必要な時まで計算を行わない。つまり、評価するまではメモリを消費しない。

そのため

  • メモリ効率が良い
  • 大量データの処理に適してる

の利点がある。

ジェネレータ式の使い方

ジェネレータ式の具体的な使い方をまとめる

構文

ジェネレータ式の構文は

ジェネレータ式の構文
(for 変数 in iterable if 条件)
  • 式:作成するジェネレータオブジェクトに追加する要素
  • 変数:iterableからの要素を一時的に保持する変数
  • 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}
]
# ジェネレータ式でジェネレータオブジェクト生成
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()を使う場合は下記のようになる。

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)) # Tujimura
print(next(user_names)) # mori
print(next(user_names)) # shimada
print(next(user_names)) # kyogoku
print(next(user_names)) # StopIteration

nextを使って順次、ジェネレータオブジェクトを遅延評価して取り出す。
値がなくなるとStopIterationとなる。

注意点

ジェネレータオブジェクトを注意点として

  • 一度使用したジェネレータオブジェクトは、再度繰り返して使うことはできない

というのがあるので注意。

注意点
# nextで遅延評価して値を取り出す
print(next(user_names)) # Tujimura
print(next(user_names)) # mori
print(next(user_names)) # shimada
print(next(user_names)) # kyogoku
print(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}
]
user_names = (user["name"] for user in users)
print(list(user_names)) # ['Tujimura', 'mori', 'shimada', 'kyogoku']

ただし、ジェネレータ式の利点(特に 遅延評価 と メモリ効率)は、list() を使ってすべての要素を一気に取り出す
となくなる。

データサイズが小さいや再利用したい場合に使う。
※その場合はリスト内表表記を使ったほうがいい。

年齢が20歳以下のユーザーのジェネレータオブジェクトを作成する

ジェネレータ式_2
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"]) # mori
print(next(adult_users,None)) # None
  • 取り出し後はディクショナリなのでキー指定で属性をとれる
  • nextで取得できなかった場合の処理を書くこと可能。

リスト内包表記との違い

最後にリスト内包表記との違いをまとめておく。

式の違い

リスト内包表記は

リスト内包表記
[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のジェネレータ式の使い方をまとめてみた。
処理するデータによってうまくリスト内包表記と使い分ける
必要がありそう。

参考書籍

関連記事

新着記事

タグ別一覧
top