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

error
この記事は基本情報技術者試験の旧制度( 2022 年以前)の記事です。
この記事の題材となっている「午後問題」は現在の試験制度では出題されません。 ご注意くださいませ。
この連載では、プログラミングの入門者を対象として、基本情報技術者試験の出題範囲にテーマを絞って、 Python の言語構文とプログラムの読み方を説明します。
今回のテーマは、ジェネレータと yield 文 です。やや高度なテーマなのですが、前回のラムダ式と同様に、試験の出題範囲を示したシラバスにあるものなので、しっかりとマスターしておきましょう。
-
(7)関数の定義
利用者の定義による関数を用いて構造化されたプログラムを作成する。修得項目def 文,return 文,ジェネレータ,yield 文,ラムダ式,再帰呼出し,デコレータなど
ジェネレータとは?
Python では、以下のように、 for 文の in の後にリストやタプルなどのイテラブル(複数の要素を反復して取り出せるオブジェクト)を置くと、要素が 1 つずつ取り出されます。
この記事で示すプログラムは、 Python の対話モードで実行しています。説明の都合で、プログラムの行末にコメントを付けていますが、実際にプログラムを動作させるときには、これらのコメントは不要です。
infoお手元に Python の環境がない場合、ブラウザからオンラインで実行できる「 JupyterLab 」で、文中のコードをお試しください。 play_arrow ボタンを押すか「 Shift + Enter 」で実行できます
>>> 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 関連タグ
免除試験を受けた 74.9% の方が、 科目A免除資格を得ています。
※独習ゼミは、受験ナビ運営のSEプラスによる試験対策eラーニングです。

基本情報ではじめる Python 最終回 試験問題レベルにチャレンジ
update
基本情報ではじめる Python (9) import 文とライブラリの活用
update
基本情報ではじめる Python (8) 組み込み関数
update
基本情報ではじめる Python (7) generator と yield 文
update
基本情報ではじめる Python (6) ラムダ式 って何? どんな用途で使うの?
update
基本情報ではじめる Python (5) オブジェクト指向
update
基本情報ではじめる Python (4) if ~ else と while / for
update
基本情報ではじめる Python (3) 引数 ~ Python の関数の引数には様々な形式がある
update
基本情報ではじめる Python (2) 配列 ~ リスト、タプル、文字列、集合、辞書の特徴
update
基本情報ではじめる Python (1) 基本構文 ~ 擬似言語と比べて覚える
update
『プログラムはなぜ動くのか』(日経BP)が大ベストセラー
IT技術を楽しく・分かりやすく教える“自称ソフトウェア芸人”
大手電気メーカーでPCの製造、ソフトハウスでプログラマを経験。独立後、現在はアプリケーションの開発と販売に従事。その傍ら、書籍・雑誌の執筆、またセミナー講師として活躍。軽快な口調で、知識0ベースのITエンジニアや一般書店フェアなどの一般的なPCユーザの講習ではダントツの評価。
お客様の満足を何よりも大切にし、わかりやすい、のせるのが上手い自称ソフトウェア芸人。
主な著作物
- 「プログラムはなぜ動くのか」(日経BP)
- 「コンピュータはなぜ動くのか」(日経BP)
- 「出るとこだけ! 基本情報技術者」 (翔泳社)
- 「ベテランが丁寧に教えてくれる ハードウェアの知識と実務」(翔泳社)
- 「ifとelseの思考術」(ソフトバンククリエイティブ) など多数