基本情報ではじめる Python (7) generator と yield 文


2021-11-19 更新

この連載では、プログラミングの入門者を対象として、基本情報技術者試験の出題範囲にテーマを絞って、 Python の言語構文とプログラムの読み方を説明します。

今回のテーマは、ジェネレータと yield 文 です。やや高度なテーマなのですが、前回のラムダ式と同様に、試験の出題範囲を示したシラバスにあるものなので、しっかりとマスターしておきましょう。


  • (7)関数の定義

    利用者の定義による関数を用いて構造化されたプログラムを作成する。

    修得項目

    def 文,return 文,ジェネレータ,yield 文,ラムダ式,再帰呼出し,デコレータなど

出典基本情報技術者試験シラバス Ver.7.2 より

ジェネレータとは?

Python では、以下のように、 for 文の in の後にリストやタプルなどのイテラブル(複数の要素を反復して取り出せるオブジェクト)を置くと、要素が 1 つずつ取り出されます。

この記事で示すプログラムは、 Python の対話モードで実行しています。説明の都合で、プログラムの行末にコメントを付けていますが、実際にプログラムを動作させるときには、これらのコメントは不要です。

infoお手元に Python の環境がない場合、ブラウザからオンラインで実行できる「 JupyterLab 」で、文中のコードをお試しください。 play_arrow ボタンを押すか「 Shift + Enter 」で実行できます

info編集部注: スマートフォンでご覧の際は、プログラムは横スクロールすると全文をご覧になれます

>>> animals = ['dog', 'cat', 'mouse']	# リストを作成する
>>> for a in animals:			        # for文で要素を1つずつ取り出す
...     print(a)			            # 要素を表示する
...					                    # [Enter]キーだけを押す
dog					                    # 実行結果が表示される
cat
mouse
>>> numbers = (111, 222, 333)	# タプルを作成する
>>> for n in numbers:			# for文で要素を1つずつ取り出す
...     print(n)			    # 要素を表示する
...					            # [Enter]キーだけを押す
111					            # 実行結果が表示される
222
333

リストやタプルは、あらかじめ Python に組込まれているものですが、それらと同様の機能を持つオブジェクトを独自に作成することができ、これをジェネレータと呼びます。

ジェネレータ( generator )は、「生成装置」という意味です。ジェネレータは、複数の要素を反復して取り出せるオブジェクトです。リストやタプルと同様に、 for 文の in の後にジェネレータを置くことができます。

ジェネレータ関数と yield 文

ジェネレータは、ジェネレータ関数によって作成されます。これは、言葉で説明するより、具体例を示した方が早いでしょう。

以下は、 'dog''cat''mouse' という文字列を順番に返す animal_generator というジェネレータ関数の定義です。

通常の関数では、戻り値を return 文で返しますが、ジェネレータ関数では、 yield 文で返します。 yield は、「生産する」という意味です。 3 つ並んだ yield 文は、要素を反復して取り出すときに、上から順に実行されます。

>>> def animal_generator():	# ジェネレータ関数の定義
...     yield 'dog'		    # 1つ目の要素を返す
...     yield 'cat'		    # 2つ目の要素を返す
...     yield 'mouse'	    # 3つ目の要素を返す
...				            # [Enter]キーだけを押す

戻り値 = ジェネレータ関数() という構文でジェネレータ関数を呼び出すと、戻り値としてジェネレータが得られます。

以下では、

animal = animal_generator()

の部分で、 animal_generator 関数が返したジェネレータを、 animal に格納しています。この animal を for 文の in の後に置くと、要素を 1 つずつ得ることができます。

>>> animal = animal_generator()	# ジェネレータオブジェクトを得る
>>> for a in animal:            # for文で要素を1つずつ取り出す
...     print(a)		        # 要素を表示する
...				                # [Enter]キーだけを押す
dog				                # 実行結果が表示される
cat
mouse

以下のように、ジェネレータ関数を for 文の in の後に置くこともできます。

>>> for a in animal_generator():	# ジェネレータ関数をfor文のinの後に置く
...     print(a)	                # 要素を表示する
...					                # [Enter]キーだけを押す
dog					                # 実行結果が表示される
cat
mouse

Python の組み込み関数である list 関数の引数にジェネレータ関数の呼び出しを置くと、ジェネレータが返す要素をリストに変換することもできます。

>>> animals = list(animal_generator())	# ジェネレータの要素をリストに変換する
>>> animals				                # リストの内容を表示する
['dog', 'cat', 'mouse']		            # 実行結果が表示される

繰り返し処理の中で yield 文を使う

プログラム(すぐ後で例を示します)を見ると不思議な感じがするかもしれませんが、ジェネレータ関数に繰り返し処理を記述して、その中で yield 文を使うこともできます。

この場合には、繰り返し処理がまとめて実行されるのではなく、ジェネレータ関数が返したジェネレータから要素が 1 つずつ取り出されるときに、繰り返し処理の中にある yield 文が 1 回ずつ呼び出されます

 

以下は、 Python の組み込み関数である range 関数と同様の機能を持つ、 range_generator(start, stop, step) というジェネレータ関数の定義です。

start から stop 未満まで step 間隔の値を返します。 while 文の繰り返し処理の中に yield 文があることに注目してください。この yield 文は、ジェネレータから要素が 1 つずつ取り出されるときに、 1 回ずつ呼び出されます。

>>> def range_generator(start, stop, step):	# ジェネレータ関数の定義
...     n = start
...     while n < stop:
...         yield n				            # ここに注目!
...         n += step
...						                    # [Enter]キーだけを押す

以下は、 for 文の in の後に、 range_generator(0, 10, 1) を置き、ジェネレータから取り出した要素を表示するプログラムです。 0 から 10 未満まで 1 間隔の値が順番に得られています。

for i in range_generator(0, 10, 1):	# ジェネレータ関数をfor文のinの後に置く
...     print(i)			        # 要素を表示する
...					                # [Enter]キーだけを押す
0					                # 実行結果が表示される
1
2
3
4
5
6
7
8
9

ジェネレータが役立つ場面(その 1 )

ジェネレータは、要素が要求される場面でその都度データを産出する( yield する)ので、メモリを多く消費しないという利点があります。

たとえば、学生時代の数学の教科書に掲載されていた 1 ~ 100 の平方表( 1 ~ 100 を 2 乗した値の一覧表)を返す関数を作成するとしましょう。以下のように、 1 ~ 100 の平方表のリストを返す関数(ジェネレータ関数ではない通常の関数)として作成すると、リストの大きさ分のメモリを消費します。

>>> def square_func():			    # 平方表のリストを返す関数の定義
...     sq_list = []			    # 空のリストを作成する
...     n = 1
...     while n <= 100:
...         sq_list.append(n ** 2)	# リストに要素を追加する
...         n += 1
...     return sq_list			    # 要素数100個のリストを返す
...					                # [Enter]キーだけを押す
>>> for data in square_func():	# 要素を1つずつ取り出す
...     print(data)			    # 要素を表示する
...					            # [Enter]キーだけを押す
1					            # 実行結果が表示される
4
9
16
25
# 中 略
9604
9801
10000

同じ目的の関数を、以下のようにジェネレータ関数として作成すれば、要素が要求される場面でその都度データを算出するので、メモリを多く消費しません。

>>> def square_generator():		# ジェネレータ関数の定義
...     n = 1
...     while n <= 100:
...         yield n ** 2		# 平方の値を算出する
...         n += 1
...					            # [Enter]キーだけを押す
>>> for data in square_generator():	# 要素を1つずつ取り出す
...     print(data)			        # 要素を表示する
...					                # [Enter]キーだけを押す
1					                # 実行結果が表示される
4
9
16
25
# 中 略
9604
9801
10000

ジェネレータが役立つ場面(その 2 )

ジェネレータ関数は、現在の状態を保持できる関数だといえます。したがって、直前に算出したデータの値を、次に算出するデータの演算で使う、ということができます。これは、通常の関数には、できないことです。通常の関数は、呼び出されるたびに、まっさらな状態で処理を行うからです。

以下は、 1 から 100 までの階乗を算出するジェネレータ関数 fact_generator の定義です。直前に算出したデータの値を変数 prev に格納しているので、変数 n の階乗を prev * n という演算で求めることができます。

>>> def fact_generator():		# ジェネレータ関数の定義
...     prev = 1			    # 直前の値の初期値
...     n = 1				    # 現在の値の初期値
...     while n <= 10:
...         fact = prev * n		# nの階乗を演算する
...         yield fact			# nの階乗を算出する
...         prev = fact			# 直前に産出した値を記憶する
...         n += 1
...					            # [Enter]キーだけを押す
>>> for data in fact_generator():	# 要素を1つずつ取り出す
...     print(data)			        # 要素を表示する
...					                # [Enter]キーだけを押す
1					                # 実行結果が表示される
2
6
24
120
720
5040
40320
362880
3628800

いかがでしたか。ジェネレータと yield 文の機能を、しっかりとマスターできたでしょう。

なお、リストやタプルからの要素の取り出しは、ジェネレータではなく、同様の機能をクラスで定義したイテレータで実現されています。イテレータの作り方に関するキーワードは、基本情報技術者試験のシラバスに示されていなので、この連載では取り上げませんが、興味がある人は、ぜひ調べてみてください。

それでは、またお会いしましょう!

 

label 関連タグ
Q. 午前試験を
『免除』するには?
A. 独習ゼミで午前免除制度を活用しましょう。
免除試験を受けた 87% の方が、
1 年間の午前免除資格を得ています。
2022 年 上期 試験向け
コース申込受付中!
info_outline
2022 年 上期 試験向け
コース申込受付中!
詳しく見てみるplay_circle_filled
label これまでの『基本情報ではじめるPython』の連載一覧 label 著者