Python: イテレータとジェネレータ

最終更新:2026-09-22

これまで for x in list: を使ってデータを反復処理してきましたが、in の後には何が来るのか考えたことはありますか?なぜ range(1000000) はメモリを消費しないのでしょうか?イテレータとジェネレータがその答えです。これらにより、「無限」のデータストリームを処理できます——必要なときに生成され、メモリに保存されることはありません。


関連する組み込み関数:reversed() iter() next() return yield for

1. イテレータプロトコルとは

Python では、for ループで使用できるオブジェクトはすべてイテラブルです。これらはイテレータプロトコルを実装しています:

▶ サンプル:イテレータプロトコルの基本

▶ 試してみよう

出力:

TEXT 📖 参照専用
# 执行成功

for ループは本質的にこれを実行しています:

▶ サンプル:for ループを手動でシミュレート

▶ 試してみよう

出力:

TEXT 📖 参照専用
# 执行成功


2. カスタムイテレータ

__iter__ と __next__ を実装して、クラスを for ループで使えるようにします:

▶ サンプル:カウントダウンイテレータ

▶ 試してみよう

出力:

TEXT 📖 参照専用
# 函数定义成功

▶ サンプル:フィボナッチイテレータ(難易度 ⭐⭐)

▶ 試してみよう

出力:

TEXT 📖 参照専用
# 函数定义成功


3. ジェネレータ:yield

__iter__ と __next__ を持つクラスを書くのは面倒です。ジェネレータ関数は yield を使って同じことを実現します:

▶ サンプル:カウントダウンジェネレータ

▶ 試してみよう

出力:

TEXT 📖 参照専用
# 函数定义成功

yield は return とは異なります——return は関数を終了しますが、yield は関数を一時停止し、現在の状態を記憶し、次回の呼び出し時に中断したところから再開します。

▶ サンプル:フィボナッチジェネレータ

▶ 試してみよう

出力:

TEXT 📖 参照専用
# 函数定义成功
💡 ジェネレータ vs イテレータクラス: ジェネレータ関数(yield)は通常、手書きのイテレータクラスよりはるかに簡潔です。ほとんどのシナリオでは、ジェネレータが第一選択です。複雑な状態を維持する必要がある場合や追加のメソッドが必要な場合にのみ、イテレータクラスの作成を検討してください。



4. ジェネレータの遅延読み込み

ジェネレータの最大の利点——すべてのデータを一度に生成するのではなく、必要に応じて生成することです:

▶ サンプル:ジェネレータの遅延読み込み

▶ 試してみよう

出力:

TEXT 📖 参照専用
Generating 1
Generating 2
Generating 3
Generator created, but not yet executed

実行の流れを見てください。next() を呼び出すたびに、次の yield まで実行され、一時停止します。これは大規模データで非常に便利です:

▶ サンプル:大きなファイルを行ごとに読み取り

TEXT 📖 参照専用
# 大きなファイルを処理する「遅延」方法
def read_large_file(filename):
    """大きなファイルを行ごとに読み取り、すべてをメモリに読み込まない"""
    with open(filename, "r", encoding="utf-8") as f:
        for line in f:
            yield line.strip()

# ジェネレータで処理 — 一度に 1 行のみメモリに保持
for line in read_large_file("huge_log.txt"):
    if "ERROR" in line:
        print(line)  # 速度はディスク読み取り速度のみに制限される


5. ジェネレータ式

リスト内包表記と似ていますが、括弧を使用します——ジェネレータ式は遅延評価されます:

▶ サンプル:ジェネレータ式

▶ 試してみよう

出力:

TEXT 📖 参照専用
# 执行成功
比較 リスト内包表記 [] ジェネレータ式 ()
メモリ すべてのデータがメモリに 必要に応じて、ほぼゼロ
速度 高速な生成とアクセス 遅延、毎回計算
再利用 複数回反復可能 1 回のみ反復可能
使用ケース 小規模データ、繰り返し使用 大規模データ、1 回だけの処理
💡 ジェネレータを使うタイミング: ① 大規模データセット(数千項目以上)。② 1 回だけ反復すれば十分な場合。③ ストリーミング処理が必要な場合(行ごとのファイル、リアルタイムデータストリーム)。④「無限シーケンス」を表現したい場合。



6. よくあるユースケース


関連する組み込み関数:reversed() iter() next() return yield for

❓ よくある質問

Q ジェネレータと通常の関数の違いは何ですか?
A 通常の関数は return を使って結果を 1 回返し、終了します。ジェネレータは yield を使って 1 つずつ値を生成し、各 yield の後に一時停止して状態を保存します。ジェネレータ関数を呼び出すたびに、新しいジェネレータオブジェクトが返されます。
Q ジェネレータは 1 回しか反復できません。複数回反復する必要がある場合はどうすればよいですか?
A list(gen) でジェネレータをリストに変換します。ただし、データが大きすぎる場合、リストに変換するとメモリが枯渇します。妥協策:itertools.tee() でジェネレータをコピーするか、ジェネレータ関数を再度呼び出して新しいジェネレータを作成します。
Q yield と return は共存できますか?
A はい——ジェネレータ内の return は効果的に反復を停止し、オプションの値とともに StopIteration を発生させます。ただし、ほとんど使われません。yield と return の両方を使う関数が必要な場合は、設計を見直してください。

📖 まとめ


📝 練習問題

  1. 基本(難易度 ⭐):ジェネレータ even_numbers(max_n) を書いてください。0 から max_n までのすべての偶数を生成します。

  2. 中級(難易度 ⭐⭐):ジェネレータ repeat_list(items, times) を書いてください。リストの各要素を times 回繰り返します。例:list(repeat_list([1, 2, 3], 2)) は [1, 1, 2, 2, 3, 3] を返します。

  3. 上級(難易度 ⭐⭐⭐):ジェネレータ deep_flatten(nested) を書いてください。任意の深さのネストしたリストを平坦化します。例:list(deep_flatten([1, [2, [3, 4]], 5])) は [1, 2, 3, 4, 5] を返します。ヒント: 要素を反復処理し、要素がリストの場合は yield from で再帰的に yield します。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%