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

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

作成日:2024月12月02日
更新日:2025年03月26日

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はジェネレータオブジェクトであるため
使うには値を取り出す必要がある
※このままでは何もできない

遅延評価とは?

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

  • next
  • list
  • 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件のみ)

ジェネレータ式を使ってリストの中からデータを1件抽出して
各種の遅延評価を行う

辞書リストから抽出(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}
]
# ジェネレータ式で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で取り出しているのでオブジェクトで取得できる

辞書リストから抽出(list)

py
# 辞書リスト
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で取得できる

辞書リストから抽出(for)

for
# 辞書リスト
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文内では遅延評価が実行される

インスタンスリストから抽出(next)

next
from pydantic import BaseModel
# pydanticデータモデル
class User(BaseModel):
user_id: int
name: str
age: 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 users
if user.user_id == 1),
None
)
print(terget_user.model_dump())
# 実行結果
# {'user_id': '1', 'name': 'Tujimura', 'age': 11}

インスタンスリストから抽出(list)

list
from pydantic import BaseModel
# pydanticデータモデル
class User(BaseModel):
user_id: int
name: str
age: 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 users
if user.user_id == 1)
)
print(terget_user)
# 実行結果
# [User(user_id=1, name='Tujimura', age=11)]

listで取得できる

インスタンスリストから抽出(for)

for
from pydantic import BaseModel
# pydanticデータモデル
class User(BaseModel):
user_id: int
name: str
age: 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 users
if user.user_id == 1)
)
# forで遅延評価
for user in terget_user:
print(user)
# 実行結果
# user_id=1 name='Tujimura' age=11

データのフィルタリングをする(複数件)

ジェネレータ式を使ってリストの中からデータを複数抽出して
各種の遅延評価を行う

辞書リストから抽出(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}
]
# ジェネレータ式で抽出
target_users = (user for user in users if user["age"] > 20)
# next() を取得件数分呼び出す
while True:
# 遅延評価
user = next(target_users, None) # 次の要素を取得
if user is None: # 取得できなくなったら終了
break
print(user)
# 実行結果
# {'user_id': '3', 'name': 'shimada', 'age': 50}
# {'user_id': '4', 'name': 'kyogoku', 'age': 70}

取得件数分、nextで取り出し続ける

辞書リストから抽出(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}
]
# ジェネレータ式を 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}
# ]

リストの場合はすべての要素をリスト化して使い回せるが
大量データの場合はメモリを圧迫する可能性がある

辞書リストから抽出(for)

for
# 辞書リスト
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}

インスタンスリストから抽出(next)

next
from pydantic import BaseModel
# pydanticデータモデル
class User(BaseModel):
user_id: int
name: str
age: 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: # 取得できなくなったら終了
break
print(user)
# 実行結果
# {'user_id': '3', 'name': 'shimada', 'age': 50}
# {'user_id': '4', 'name': 'kyogoku', 'age': 70}

インスタンスリストから抽出(list)

list
from pydantic import BaseModel
# pydanticデータモデル
class User(BaseModel):
user_id: int
name: str
age: 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]

インスタンスリストから抽出(for)

for
from pydantic import BaseModel
# pydanticデータモデル
class User(BaseModel):
user_id: int
name: str
age: 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

データの属性を絞って抽出する

ユーザー名だけのリストを生成する(一気に取り出す)

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

参考書籍

関連記事

新着記事

目次
タグ別一覧
top