Python
 Computer >> コンピューター >  >> プログラミング >> Python

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のジェネレーターをプロのように使いこなせるようになりました!

  1. Pythonでパターンを出力する方法|三角形・数字・アルファベット・ピラミッドのコード例

    Pythonでパターンを出力するには、ネストされた(入れ子構造の)forループを使用します。外側のループで行数を制御し、内側のループで列数を処理します。そしてprint文を工夫することで、目的に応じたさまざまなパターンを作成できます。パターンには、星(アスタリスク)パターン、数字パターン、アルファベットパターンなどがあり、三角形やピラミッドなど、さまざまな形状にも対応できます。パターン出力の基本これらのパターンは、print文を少し変更しながらforループを組み合わせることで出力できます。基本的な考え方はどのパターンも共通しており、違いはごくわずかです。ここからは、代表的なパターンのコードを実

  2. Pythonプログラムの実行方法まとめ!IDLE・コマンドライン・PyCharmなど5つの方法を解説

    コードを書いたら、次はそれを実行して出力結果を確認する必要があります。プログラムを実行することで、書いたコードが正しく動作し、期待通りの結果が得られるかどうかをチェックできます。幸いなことに、Pythonプログラムの実行はとても簡単です。この記事では、代表的な5つの実行方法をわかりやすく解説します。IDLEで実行するPythonに標準で付属しているIDLEを使ってプログラムを実行するには、以下の手順に従います。Pythonのコードを書いて保存します。メニューから「Run > Run Module」を選択するか、キーボードのF5キーを押すだけで実行できます。IDLEは初心者にとって扱いやす