Pythonにおけるジェネレーター
この記事ではPythonにおけるジェネレーターについて説明します。
YouTube Video
Pythonにおけるジェネレーター
概要
Pythonの**ジェネレーター(generator)**は、イテレーターの一種で、繰り返し処理を効率的に行うための強力な機能です。大量のデータを扱う際に、メモリ効率の良いコードを記述できるようになります。
ジェネレーターとは?
Pythonのジェネレーターは、値を一度に1つずつ生成する特殊な関数で、yield
キーワードを使って定義されます。関数の実行状態を保持したまま一時停止し、次回再開できるのが特徴です。
yield
の基本
yield
は、関数の途中で値を返しつつ実行を一時停止するためのキーワードです。
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8print(next(gen)) # 1
9print(next(gen)) # 2
10print(next(gen)) # 3
- この関数は、呼び出すと値を順番に返すジェネレーターオブジェクトを返します。
- 次の値がない状態で
next()
を呼ぶと、StopIteration
エラーが発生します。
next()
と StopIteration
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6gen = simple_generator()
7
8try:
9 while True:
10 value = next(gen)
11 print(value)
12except StopIteration:
13 print("Finished")
- このように、明示的に
StopIteration
エラーを処理することで、ジェネレーターの終了を検知できます。
send(value)
send(value)
を呼ぶと、ジェネレーターを再開させ、yield
式の位置にvalue
を渡せます。渡した値は yield
式の戻り値としてジェネレーター側で受け取れます。初回は、send(value)
で None
以外を送れないため、next()
または send(None)
を使う必要があります。
1def gen():
2 x = yield 1
3 print(f"x = {x}")
4 y = yield 2
5 print(f"y = {y}")
6
7g = gen()
8print(next(g)) # -> 1 (value from yield 1)
9print(g.send(10)) # -> x = 10, 2 (value from yield 2)
10print(g.send(20)) # -> y = 20, StopIteration occurs
send(10)
によって、ジェネレーター側のyield
が 10 を返す式になり、x
に10が代入されます。
throw()
throw
を呼び出すと、ジェネレーターを再開しつつ、停止中の yield
の位置で例外を発生させることができます。ジェネレーター内で例外を処理することで処理を継続できます。捕捉しない場合は外に例外が伝播し、ジェネレーターは終了します。
1def gen():
2 try:
3 yield 1
4 except ValueError as e:
5 print(f"Caught: {e}")
6 yield "recovered"
7
8g = gen()
9print(next(g)) # -> 1
10print(g.throw(ValueError("boom"))) # -> Caught: boom, "recovered"
- このコードでは、
throw
を呼び出して、ジェネレーターに例外を注入しています。ジェネレーター側では、例外処理をしてrecovered
を返しています。
close()
close()
を呼び出すと、ジェネレーターを終了させます。ジェネレーター内部では、finally
でクリーンアップができます。close()
を呼び出した後にnext()
やsend()
を呼ぶとStopIteration
エラーが発生します。
1def gen():
2 try:
3 yield 1
4 finally:
5 print("Cleaning up...")
6
7g = gen()
8print(next(g)) # -> 1
9g.close() # -> Cleaning up...
- このコードは、
close()
でジェネレーターを終了させ、finally
のクリーンアップ処理が実行されることを示します。
yield from
yield from
は サブジェネレーターの委譲 を行うための構文です。ジェネレーターの中で別のジェネレーターを呼び出し、すべての値をそのまま外側に流すためのシンプルな書き方です。
1def sub_gen():
2 yield 1
3 yield 2
4
5def main_gen():
6 yield from sub_gen()
7 yield 3
8
9print(list(main_gen())) # -> [1, 2, 3]
- このコードは、
yield from
でサブジェネレーターの全ての値を外側に委譲して流し、その後に3
をyield
しています。
イテレーターとの関係
ジェネレーターは __iter__()
と __next__()
を内部的に持ち、イテレーターの一種です。そのため、for
文などのイテラブルな処理と完全に互換性があります。
for
ループとの連携
Pythonでは、for
ループが内部的に next()
を使って値を自動で取得します。
1def simple_generator():
2 yield 1
3 yield 2
4 yield 3
5
6for value in simple_generator():
7 print(value)
この方法なら StopIteration
の処理も自動です。
無限ジェネレーターの作成
1def count_up(start=0):
2 while True:
3 yield start
4 start += 1
5
6counter = count_up()
7print(next(counter)) # 0
8print(next(counter)) # 1
無限ループを作成することも可能ですが、使い方には注意が必要です。
ジェネレーター式(Generator Expression)
丸括弧を用いたジェネレーター式によって、リスト内包表記のような構文で、ジェネレーターを簡単に記述できます。
1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8 print(square)
リスト内包表記と異なり全要素を一度にメモリに展開しないため、メモリ効率が良くなります。
ジェネレーターのエラーハンドリング
ジェネレーター内では例外が発生する可能性があります。その場合は、通常のPythonコードと同様にtry-except
を使用します。
1def safe_divide_generator(numbers, divisor):
2 """Yields results of dividing numbers by a given divisor safely."""
3 for number in numbers:
4 try:
5 yield number / divisor # Attempt to divide and yield result.
6 except ZeroDivisionError:
7 yield float('inf') # Return infinity if division by zero occurs.
8
9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0) # Create generator with divisor as 0.
12for value in gen:
13 print(value) # Output: inf, inf, inf
この例ではゼロ除算が発生した場合に、適切なエラーハンドリングを実行しています。
ジェネレーターのスタックトレース
ジェネレーター内で例外が起きた場合、その例外はジェネレーターが再開したときに発生します。
1def error_generator():
2 """A generator that yields values and raises an error."""
3 yield 1
4 raise ValueError("An error occurred") # Raise a ValueError intentionally.
5 yield 2
6
7gen = error_generator()
8print(next(gen)) # Output: 1 (first value yielded)
9try:
10 print(next(gen)) # Attempt to get the next value, which raises an error
11except ValueError as e:
12 print(e) # Output: An error occurred (exception message is printed)
- このジェネレータは、最初に
1
を返します。再開時に発生したエラーは捕捉され、エラーメッセージとして表示されます。
ジェネレーターの活用例
ファイルの1行ずつ読み込み(大規模ファイル対応)
1def read_large_file(filepath):
2 with open(filepath, 'r') as f:
3 for line in f:
4 yield line.strip()
- この関数はテキストファイルをイテレータで1行ずつ読み込み、各行の前後空白を除去して返すジェネレーターのため、大規模ファイルでも低メモリで処理できます。
Fibonacci数列のジェネレーター
1def fibonacci(limit):
2 a, b = 0, 1
3 while a < limit:
4 yield a
5 a, b = b, a + b
6
7for n in fibonacci(100):
8 print(n)
- このコードは、ジェネレーターで上限値未満のフィボナッチ数を順に生成し、
for
ループでそれらの数値を出力します。
活用方法
他にも次のような場面でジェネレーターを活用できます。
- 巨大なCSVやログファイルの逐次処理
- APIのページネーション
- ストリーミングデータの処理(例: Kafka, IoTデバイス)
まとめ
概念 | ポイント |
---|---|
yield |
一時停止して値を返す |
ジェネレーター関数 | yield を含む関数で、呼び出すとイテレーターになる |
メリット | メモリ効率が良い、大規模データの処理に最適 |
ジェネレーター式 | (x for x in iterable) のような簡潔な記述が可能 |
ジェネレータを利用することで、メモリを効率的に利用しながら、大規模データを処理でき、コードも簡潔に保つことができます。
YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。