Day 25:ジェネレータで「必要な分だけ順番に出す」感覚を身につける
Day 25では、 イテレータ・yield・ジェネレータ を通して、 「全部を一気に作るのではなく、必要になったタイミングで1つずつ値を出す」 という考え方を身につけていきます。
イテレータとは何かをイメージする
「順番に取り出せるもの」というざっくりしたイメージ
Pythonでは、for 文で回せるもの(リスト・タプル・文字列など)は、 「順番に要素を取り出せる」という性質を持っています。 この「順番に取り出せるもの」を、より厳密には イテレータ(iterator) と呼びます。
- イテレータは「次の値」を順番に返していく仕組みです。
for文は、内部でイテレータを使って「次の値」を取り出しています。
リストは「イテラブル(iterable)」であり、 iter() を使うと「イテレータ」に変換できます。
numbers = [1, 2, 3]
it = iter(numbers) # イテレータを取得します。
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# これ以上要素がない状態で next(it) を呼ぶと、StopIteration 例外が発生します。
Pythoniter(リスト)で「イテレータ」を作るnext(イテレータ)で「次の値」を1つずつ取り出す
という流れが、for 文の裏側で起きています。
ジェネレータとは何か:イテレータを「自分で作る」ための仕組み
ジェネレータ=「自作イテレータ」
ジェネレータ(generator) は、 「自分でイテレータを作るための、Pythonの特別な仕組み」です。
- 関数の形で書ける
yieldを使って「次の値」を順番に返すfor文で自然に回せる
という特徴があります。
イメージとしては、
- 「値を1つずつ返していく関数」
- 「呼ばれるたびに、前回の続きから動き出す関数」
です。
yield:ジェネレータの「心臓部」
returnとの違いを理解する
通常の関数は、return を使って値を返します。
def make_list():
return [1, 2, 3]
result = make_list()
print(result) # [1, 2, 3]
Python一方、ジェネレータ関数では yield を使います。
def gen_numbers():
yield 1
yield 2
yield 3
g = gen_numbers()
print(g) # <generator object gen_numbers at ...>
for n in g:
print(n)
Python出力:
1
2
3
ここでの重要ポイントは、
yieldは「ここで一旦値を返し、次に呼ばれたときはこの続きから再開する」という動きをすることgen_numbers()を呼ぶと「リスト」ではなく「ジェネレータオブジェクト」が返ってくることfor文で回すと、yieldで返された値が順番に取り出されること
です。
ジェネレータの基本形をステップバイステップで理解する
シンプルなジェネレータ関数
まずは、1〜5までの数字を順番に返すジェネレータを書いてみます。
def count_up_to_5():
"""1〜5までの数字を順番に返すジェネレータです。"""
print("ジェネレータ開始")
yield 1
print("1を返しました")
yield 2
print("2を返しました")
yield 3
print("3を返しました")
yield 4
print("4を返しました")
yield 5
print("5を返しました")
print("ジェネレータ終了")
Python使ってみます。
g = count_up_to_5()
for n in g:
print("受け取った値:", n)
Python実行イメージ:
ジェネレータ開始
受け取った値: 1
1を返しました
受け取った値: 2
2を返しました
受け取った値: 3
3を返しました
受け取った値: 4
4を返しました
受け取った値: 5
5を返しました
ジェネレータ終了
ここでのポイントは、
- ジェネレータ関数は「最初に呼ばれたときに全部実行される」のではなく、
yieldごとに「少しずつ」実行されること for文が内部でnext()を呼び、yieldに到達するたびに値を受け取っていること
です。
ジェネレータの強み:大きなデータを「少しずつ」扱う
リストで一気に作る場合との比較
例えば、「1〜1000000までの数字の2乗」を扱いたいとします。
リストで一気に作る場合:
def make_squares_list(n):
"""1〜nまでの2乗をリストで返します。"""
result = []
for i in range(1, n + 1):
result.append(i ** 2)
return result
squares = make_squares_list(1_000_000)
print(len(squares)) # 1000000
Pythonこの場合、
- 100万個の要素を一気にメモリに載せる
- メモリ使用量が大きくなる
という問題があります。
ジェネレータで少しずつ返す場合:
def gen_squares(n):
"""1〜nまでの2乗をジェネレータで返します。"""
for i in range(1, n + 1):
# ここで1つずつ yield します。
yield i ** 2
g = gen_squares(1_000_000)
# 最初の10個だけ使う
for i, value in enumerate(g, start=1):
print(value)
if i >= 10:
break
Pythonこの場合、
- 必要な分だけ順番に計算して返す
- 「全部を一気にメモリに載せる」必要がない
というメリットがあります。
ジェネレータを使った「ちょっと実用的な」例
ファイルを1行ずつ読むジェネレータ
大きなテキストファイルを扱うとき、 「全部を一気に読み込む」のではなく「1行ずつ処理する」ほうが安全です。
def read_lines(path):
"""ファイルを1行ずつ読み込むジェネレータです。"""
with open(path, "r", encoding="utf-8") as f:
for line in f:
# strip() で改行を取り除いてから返します。
yield line.strip()
Python使ってみます。
for line in read_lines("sample.txt"):
print(line)
# 必要なら途中で break してもOKです。
Pythonここでのポイントは、
with open(...)の中でfor line in f:としていることyieldによって「1行ずつ」外に渡していること- 呼び出し側は「ジェネレータを
forで回す」だけで、 大きなファイルでも安全に扱えること
です。
無限に続くジェネレータ(必要なところで止める)
ジェネレータは、「終わりのない列」を表現することもできます。
def infinite_counter(start=0):
"""startから始まる無限カウンタを返すジェネレータです。"""
n = start
while True:
yield n
n += 1
Python使ってみます。
counter = infinite_counter(10)
for i, value in enumerate(counter, start=1):
print(value)
if i >= 5:
break # 無限ジェネレータなので、どこかで止める必要があります。
Python出力:
10
11
12
13
14
ここでのポイントは、
- ジェネレータは「無限列」も表現できること
- 呼び出し側が「どこまで使うか」を決めることで、柔軟に扱えること
です。
ジェネレータ式:内包表記のように書ける形
リスト内包表記との比較
リスト内包表記:
squares_list = [n ** 2 for n in range(1, 6)]
print(squares_list) # [1, 4, 9, 16, 25]
Pythonジェネレータ式:
squares_gen = (n ** 2 for n in range(1, 6))
print(squares_gen) # <generator object ...>
for value in squares_gen:
print(value)
Python[]なら「リスト内包表記」()なら「ジェネレータ式」
という違いがあります。
ジェネレータ式は、
- 「全部を一気に作らず、必要な分だけ順番に取り出す」
- 「メモリを節約できる」
というメリットがあります。
Day 25ミニテンプレート:ジェネレータ練習スクリプト
最後に、Day 25で学んだ内容をまとめて試せる「ジェネレータ練習スクリプト」のテンプレートを示します。
# day25_generators.py
# ジェネレータ入門の練習用スクリプト
def count_up_to_5():
"""1〜5までの数字を順番に返すジェネレータです。"""
print("ジェネレータ開始")
yield 1
print("1を返しました")
yield 2
print("2を返しました")
yield 3
print("3を返しました")
yield 4
print("4を返しました")
yield 5
print("5を返しました")
print("ジェネレータ終了")
def gen_squares(n):
"""1〜nまでの2乗をジェネレータで返します。"""
for i in range(1, n + 1):
yield i ** 2
def infinite_counter(start=0):
"""startから始まる無限カウンタを返すジェネレータです。"""
n = start
while True:
yield n
n += 1
def main():
print("=== count_up_to_5 の例 ===")
g = count_up_to_5()
for value in g:
print("受け取った値:", value)
print("\n=== gen_squares の例(最初の10個だけ) ===")
squares = gen_squares(1_000_000)
for i, value in enumerate(squares, start=1):
print(value)
if i >= 10:
break
print("\n=== infinite_counter の例(start=10, 最初の5個) ===")
counter = infinite_counter(10)
for i, value in enumerate(counter, start=1):
print(value)
if i >= 5:
break
print("\n=== ジェネレータ式の例 ===")
squares_gen = (n ** 2 for n in range(1, 6))
for value in squares_gen:
print(value)
if __name__ == "__main__":
main()
PythonDay 25のまとめ
Day 25では、ジェネレータ入門として、
- イテレータ=「次の値を順番に返す仕組み」というイメージ
yieldを使って「少しずつ値を返す関数」を作る方法- 大きなデータや無限列を「必要な分だけ順番に処理する」考え方
- ファイル読み込みや数列生成など、実用的な場面でのジェネレータの使い方
- ジェネレータ式で「内包表記のように書ける形」を体験すること
をステップバイステップで学んでいただきました。
ここまで来ると、「全部を一気に作るのではなく、必要な分だけ順番に出す」という発想が少し育っているはずです。 この感覚は、今後の実践Python編で、大きなデータやストリーム処理を扱うときに、メモリを節約しつつ柔軟にコードを書くための大きな武器になっていきます。
