Pythonジェネレーターの書き方を徹底解説!yield文の基本から実践例まで
Pythonにおいて、イテレーターは非常に重要な役割を果たします。イテレーターを使うと、繰り返し処理(for inループ)でアクセスできるオブジェクトを作成でき、データを効率的に格納・操作するための優れた手段となります。
しかし、イテレーターには一つ大きな課題があります。それは、作成に多くのコードが必要になることです。便利な機能である反面、動作させるまでに書かなければならないコード量は決して少なくありません。この問題を解決してくれるのが「ジェネレーター」です。
この記事では、Pythonジェネレーターとは何か、なぜ使うべきなのかを解説し、実際にジェネレーターを実装しながらその仕組みを理解していきます。
ジェネレーターとは?
ジェネレーターとは、Pythonでイテレーターを簡単に作成するための仕組みです。関数の形式をとり、繰り返し処理が可能なオブジェクトを返します。
ジェネレーターは「シンタックスシュガー(糖衣構文)」と呼ばれることがあります。これは、ジェネレーター自体が新しい機能を追加するわけではなく、コードをより効率的に書くための手段だからです。
イテレーターと同様に、ジェネレーターも一度しか反復処理できません。これは、ジェネレーターが反復回数を記録しており、リセットできないためです。
まず、通常のPythonイテレーターを見てみましょう。
class Cookies: def __init__(self, value): self.value = value def __iter__(self): return self def __next__(self): return self.value
このコードは、Cookiesというイテレーターを作成します。このイテレーターにデータを渡すと、反復可能なオブジェクトに変換され、for inループでデータを処理できるようになります。「このコードは長すぎる」と感じた方、その通りです。実際、かなりのコード量になります。
ジェネレーターを使えば、このコードを大幅に短縮できます。上記のイテレーターをジェネレーターで書き直してみましょう。
def cookies(value): while True: yield value
コードが劇的に短くなりました。ここからは、その仕組みを詳しく見ていきます。
ジェネレーターとは、return文の代わりにyieldキーワードを持つ関数のことです。yield文は関数から値を返します。
yield文とreturn文の違いは、return文が関数の実行を完全に停止するのに対し、yield文は関数を一時停止し、その後も反復処理を継続できる点にあります。
実際に、このシンプルなジェネレーター関数を反復処理してみましょう。
for cookie in cookies("Raspberry"):
print(cookie)
実行結果:
Raspberry
Raspberry
…
このコードは、プログラムを停止するまで永遠に繰り返されます。これは、whileループが無限に実行されるためです。ほとんどの場合、これは実用的ではありません。次は、特定の処理が完了した時点で停止するジェネレーターを書いてみましょう。
Pythonジェネレーターの書き方
コンソールに出力したいクッキーのリストがあるとします。リストは以下の通りです。
["Raspberry", "Choc-Chip", "Cinnamon", "Oat"]
これらをコンソールに出力するには、シンプルなジェネレーターを作成します。新しいPythonファイルを開いて、以下のコードを貼り付けてください。
def print_cookies(cookies): length = len(cookies) for cookie in range(0, length): yield cookies[cookie]
このジェネレーターは、指定されたリスト内のすべてのクッキーを順番に処理し、1つずつ値を返します。ただし、このままではジェネレーターは動作しません。forループを使って反復処理する必要があります。
cookie_list = ["Raspberry", "Choc-Chip", "Cinnamon", "Oat"] for c in print_cookies(cookie_list): print(c)
ここでは、4つのクッキーを格納する配列cookie_listを定義し、その後、ジェネレーターを使ってcookie_list内の値を反復処理するforループを設定しています。
forループの各反復処理で、生成されたオブジェクトがコンソールに出力されます。
Raspberry
Choc-Chip
Cinnamon
Oat
成功です!このジェネレーターは、cookie_list変数内のすべての値が反復処理されるまで、値を返し続けます。
ジェネレーターにはさまざまな用途があります。例えば、注文金額のリストの合計を計算したい場合、以下のようなコードが使えます。
def total_orders(orders): length = len(orders) total = 0 for o in range(0, length): total += orders[o] yield total
このジェネレーターは、リスト内のすべての注文の合計額を計算していきます。各項目が反復処理されるたびに、その時点での注文の累計額がyieldされます。このジェネレーターを使うforループを書いてみましょう。
order_list = [2.30, 2.50, 1.95, 6.00, 7.50, 2.15] for order in total_orders(order_list): print(order)
実行結果:
2.3
4.8
6.75
12.75
20.25
22.4
このジェネレーターは、order_list変数内のすべての注文の累計額を計算しました。リストを反復処理するたびに、新しい累計値が返されます。
ジェネレーター式の書き方
ジェネレーターは、すでにイテレーターよりもはるかに書きやすいものですが、ジェネレーター式を使えば、さらにクリーンなコードを書くことができます。
ジェネレーター式はリスト内包表記に似ています。ただし、ジェネレーター式はジェネレーターと同様に1つずつ項目を生成するのに対し、リスト内包表記はリスト全体を一度に生成する点が異なります。
クッキーの例でジェネレーター式を書いてみましょう。
cookie_list = ["Raspberry", "Choc-Chip", "Cinnamon", "Oat"] cookie_generator = (cookie for cookie in cookie_list)
変数cookie_listにクッキーのリストを定義し、その後ジェネレーター式を作成しています。この式はリスト内包表記と同じ構文を使いますが、大きな違いが一つあります。リスト内包表記は角括弧[ ]で定義するのに対し、ジェネレーター式は丸括弧( )で定義します。
この式を反復処理するforループを作成してみましょう。
for cookie in cookie_generator: print(cookie)
実行結果:
Raspberry
Choc-Chip
Cinnamon
Oat
このジェネレーターの出力は、最初の例と同じ結果ですが、構文は格段にすっきりしています。
重要なポイントとして、ジェネレーター式で宣言されたジェネレーターを反復処理する場合、関数として呼び出す必要はありません。cookie_generatorは入力値を受け取らず、cookie_listを反復処理するために必要なコードをすでに内包しています。
ジェネレーター式の構文は、シンプルな処理を行うジェネレーターを書く場合にのみ使用すべきです。値のリストを出力するのは良い例ですし、リスト内の値に特定の数値を掛けるのも良い例です。
これは、ジェネレーター式の構文は明快であるものの、主に1行の式のために設計されているためです。ジェネレーターが複数行のコードを使用する場合は、この記事の前半で説明した構文を使って、ジェネレーター関数として記述しましょう。
ジェネレーターが使われる理由
ジェネレーターが使われる主な理由は、イテレーターよりも簡潔に書けるからです。
ジェネレーターは関数のようなもので、動作するために__init__、__iter__、__next__の各メソッドを必要としません。これは、3つすべてのメソッドが必要なイテレーターとは対照的です。このため、イテレーターを書く場合と比べて、はるかに少ない行数でジェネレーターを実装できます。
さらに、ジェネレーターはメモリを効率的に使用します。ジェネレーターは一度に1つの項目しか生成せず、結果を返す前にシーケンス全体をメモリに作成することはありません。このため、大量のデータを反復処理する必要がある場合に、ジェネレーターは非常に実用的です。
この仕組みは「遅延評価(lazy evaluation)」と呼ばれる手法によるものです。ジェネレーターは、要素が要求されたときにのみ、反復可能なオブジェクトの次の要素を生成します。
まとめ(とチャレンジ)
ジェネレーターを使えば、よりPythonらしい(Pythonicな)イテレーターを作成できます。
ジェネレーターを使うと、__init__、__iter__、__next__メソッドを書かずに、イテレータープロトコルに準拠したオブジェクトを簡単に作成できます。ジェネレーターは関数として定義され、yield文を使って処理を一時停止し、値をメインプログラムに返した後、通常の処理に戻ります。
理解を深めるためのチャレンジ課題をいくつか紹介します。
- 文字列を逆順にするジェネレーターを書いてみよう
- 値のリストから、特定の単語を含む値のみを返すジェネレーターを書いてみよう
- リスト内のすべての数値を2倍にするジェネレーターを書いてみよう
さらに学びたい方は、Pythonイテレーターの書き方と使い方に関するチュートリアルもチェックしてみてください。これで、あなたもPythonのジェネレーターをプロのように使いこなせるようになりました!
-
Pythonでパターンを出力する方法|三角形・数字・アルファベット・ピラミッドのコード例
Pythonでパターンを出力するには、ネストされた(入れ子構造の)forループを使用します。外側のループで行数を制御し、内側のループで列数を処理します。そしてprint文を工夫することで、目的に応じたさまざまなパターンを作成できます。パターンには、星(アスタリスク)パターン、数字パターン、アルファベットパターンなどがあり、三角形やピラミッドなど、さまざまな形状にも対応できます。パターン出力の基本これらのパターンは、print文を少し変更しながらforループを組み合わせることで出力できます。基本的な考え方はどのパターンも共通しており、違いはごくわずかです。ここからは、代表的なパターンのコードを実
-
Pythonプログラムの実行方法まとめ!IDLE・コマンドライン・PyCharmなど5つの方法を解説
コードを書いたら、次はそれを実行して出力結果を確認する必要があります。プログラムを実行することで、書いたコードが正しく動作し、期待通りの結果が得られるかどうかをチェックできます。幸いなことに、Pythonプログラムの実行はとても簡単です。この記事では、代表的な5つの実行方法をわかりやすく解説します。IDLEで実行するPythonに標準で付属しているIDLEを使ってプログラムを実行するには、以下の手順に従います。Pythonのコードを書いて保存します。メニューから「Run > Run Module」を選択するか、キーボードのF5キーを押すだけで実行できます。IDLEは初心者にとって扱いやす