Pythonにおけるジェネレータについて【初心者向け解説記事】
Workteria(ワークテリア)では難易度の高いものから低いものまで、スキルや経験に合わせた案件を多数揃えています。会員登録は無料ですので、ぜひ会員登録してご希望の案件を探してみてください!
フリーランス/正社員のエンジニアとして活躍するには、ご自身のスキルや経験に合わせた仕事を選ぶことが大切です。ご希望の案件がみつからない場合はお気軽にお問い合わせください!ユーザ満足度の高いキャリアコンサルタントが在籍していますので、希望条件や悩み事などなんでもご相談ください。ご希望にピッタリの案件をご紹介させていただきます。
ジェネレータとは?
ジェネレータの定義
ジェネレータとは、Pythonに特徴的な要素の一つで、反復可能なオブジェクト(イテレータ)を簡単に作ることができる機能です。ジェネレータは通常の関数と同じように見えますが、その動作は異なります。通常の関数が結果を一度に返すのに対し、ジェネレータは一度に一つの結果を生成(yield)し、必要に応じて後から続きの結果を生成することができます。ジェネレータは、実はイテレータの特別な形の一種で、イテレータの作成をより容易にしています。
ジェネレータの利点
ジェネレータの最大の利点は、メモリ効率性です。ジェネレータは全ての結果をメモリに保持しないため、大規模なデータを扱う際に役立ちます。また、結果が必要となるまで計算を遅延するため、パフォーマンスの向上にも貢献します。
Pythonでのジェネレータの作成
ジェネレータ関数の定義
ジェネレータを作成するには、通常の関数定義と同じくdefキーワードを使用しますが、関数内部にyieldキーワードを含めます。以下に呼び出すたびに連続した整数を生成するジェネレータ関数の例を示します。
def simple_generator():
n = 0
while True:
yield n
n += 1
このジェネレータは明確な終了条件を持たず、無限に値を生成することが出来ます。
yieldの使用方法
ジェネレータ関数内部で用いるyieldは、結果を外部に返すとともに、次回呼び出されるまでジェネレータ関数の状態(ローカル変数など)を一時停止します。
ジェネレータの操作
ジェネレータからの値の取得
ジェネレータから値を取り出すためには、next関数を使用します。上記のsimple_generatorから値を取り出す例は以下のようになります。
gen = simple_generator()
print(next(gen)) # 0
print(next(gen)) # 1
ジェネレータの再開
next関数を呼び出すと、ジェネレータは前回yieldした地点から実行を再開します。
ジェネレータの終了
ジェネレータ関数がreturnステートメントに達するか、コードの最後に達すると、StopIteration例外が発生し、ジェネレータは終了します。Pythonでは、forループが自動的にこの例外をハンドリングしてループを終了します。しかし、next関数を直接使ってジェネレータを操作する場合には、自分でStopIteration例外を捕捉する必要があります。
ジェネレータとループ
forループを使ったジェネレータの操作
ジェネレータはイテレータでもあるため、forループを用いて値を取り出すことができます。ただし、前述のsimple_generator()のような無限に値を生成するジェネレータの場合、ループが終わらなくなるため注意が必要です。以下に、明示的な終了条件を持つジェネレータの例を示します。
def finite_generator(n):
for i in range(n):
yield i
for i in finite_generator(5):
print(i) # 0 1 2 3 4
ジェネレータ式とリスト内包表記の比較
ジェネレータは、リスト内包表記と似た構文を持つジェネレータ式もサポートしています。ジェネレータ式はリスト内包表記と同じく簡潔な記述が可能ですが、リスト内包表記が全ての結果をリストとしてメモリに保持するのに対し、ジェネレータ式は必要なときに値を生成します。このため、ジェネレータ式は大規模なデータを扱う際にメモリ効率が良くなります。
# リスト内包表記
list_comp = [i ** 2 for i in range(5)]
# ジェネレータ式
gen_exp = (i ** 2 for i in range(5))
ジェネレータの高度な活用
外部から値を受け取る(sendメソッド)
ジェネレータは値を外部に送るだけでなく、外部から値を受け取ることも可能です。これは、ジェネレータのsendメソッドを使用して行います。この機能は、ジェネレータが外部の入力に応じて動作を変更する場合、例えばユーザーの入力やAPIの応答に応じて次の結果を生成するようなシナリオで役立ちます。
sendメソッドを用いた例を以下に示します。
def simple_generator():
n = 0
while True:
received = yield n
if received is not None:
n = received
else:
n += 1
gen = simple_generator()
print(next(gen)) # 0
print(gen.send(100)) # 100
print(next(gen)) # 101
ただし、最初の呼び出し時にはnext関数を使う必要があります。これは、ジェネレータがまだyieldを一度も実行していない状態でsendメソッドを呼び出すとTypeErrorが発生するためです。
ジェネレータを使ったデータパイプラインの作成
ジェネレータはデータの流れ(パイプライン)を作成する際にも役立ちます。パイプラインとは、データ処理の一連のステップを、各ステップが次のステップの入力を生成するように連結したものを指します。各ステップは独立したジェネレータとして実装でき、これによりコードが読みやすく、再利用可能になります。
以下に、簡単なジェネレータパイプラインの例を示します。この例では、数値のシーケンスを生成するジェネレータ、それを2倍にするジェネレータ、そして2倍にした数値が10以上になるまで出力するジェネレータ、という3つのジェネレータを連結しています。
def generate_numbers():
n = 0
while True:
yield n
n += 1
def double_numbers(numbers):
for n in numbers:
yield n * 2
def stop_at_threshold(numbers, threshold):
for n in numbers:
if n >= threshold:
break
yield n
numbers = generate_numbers()
doubled = double_numbers(numbers)
pipeline = stop_at_threshold(doubled, 10)
for number in pipeline:
print(number)
まとめ
Pythonのジェネレータは、大規模なデータを効率的に扱うための有用なツールです。また、ジェネレータの理解は非同期処理や並行処理の理解にもつながります。非同期処理では、タスクが互いに独立して実行され、一つのタスクが完了するのを待たずに次のタスクが開始されます。これは、ジェネレータが一度に一つの結果を生成し、次の結果が必要になるまで待つという動作と類似しています。したがって、ジェネレータの理解は、より複雑な非同期処理や並行処理のパターンを理解する上で有用です。
【著者】
フォワードソフト株式会社のエンジニア。Java、Python、JavaScript、C#などの言語の他、クラウドやネットワーク技術を勉強しています。PythonやVBAを使った自動化で楽をする方法を考えるのが好きです。 最近はジェネレーティブAIの業務利用に関する検証を行っています。 資格を通じて知識を吸収することを心がけており、セキュリティスペシャリスト、データベーススペシャリスト、応用情報技術者、Oracle Certified Java Programmer Gold SE 11、Pythin3 エンジニア認定試験、HTML5プロフェッショナル認定試験レベル2、AWSプラクティショナーなどの情報資格を保有しています。
正社員/フリーランスの方でこのようなお悩みありませんか?
- 自分に合う案件を定期的に紹介してもらいたい
- 週2、リモートワークなど自由な働き方をしてみたい
- 面倒な案件探し・契約周りは任せて仕事に集中したい
そのような方はぜひ、Workteriaサイトをご利用ください!
定期的にご本人に合う高額案件を紹介
リモートワークなど自由な働き方ができる案件多数
専属エージェントが契約や請求をトータルサポート