連載
» 2019年11月19日 05時00分 公開

Python入門:[Python入門]ジェネレータ関数とジェネレータイテレータの基礎 (1/2)

イテレータを戻り値とする「ジェネレータ関数」、それが返す「ジェネレータイテレータ」の使い方の基本について説明する。

[かわさきしんじ,Deep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「Python入門」のインデックス

連載目次

 前回はPythonのイテレータについて見た後に、自分でイテレータを定義した。今回はイテレータを戻り値とするジェネレータ関数と呼ばれる機構について見ていこう。

ジェネレータ関数とジェネレータイテレータ

 ジェネレータ(ジェネレータ関数)とは、イテレータを作成するための関数のことだ。ジェネレータによって作成されたイテレータのことを特に「ジェネレータイテレータ」と呼ぶこともある。またはジェネレータ関数で作成されたイテレータのことを単純に「ジェネレータ」と呼ぶこともある。本稿では、イテレータを戻り値とする関数のことを「ジェネレータ関数」と、ジェネレータ関数によって作成されたことを「ジェネレータイテレータ」として表記する。

 前回作成したイテレータは、__iter__メソッドと__next__メソッドを持つクラスとして定義した。これに対して、ジェネレータ関数は(その名の通り)関数の形で定義する。ただし、通常の関数とジェネレータ関数には大きな違いがある。

 まず、通常の関数は、それを呼び出せば、その本体に記述したコードが実行されて、その結果が戻されるが、ジェネレータ関数を呼び出すと、戻ってくるのは、ジェネレータイテレータと呼ばれるオブジェクトであることだ(Pythonの処理系がジェネレータ関数の定義を処理する際に、そのように特別扱いしてくれる)。

ジェネレータ関数とジェネレータイテレータ ジェネレータ関数とジェネレータイテレータ

 作成されたジェネレータイテレータはイテレータなので、__iter__メソッドや__next__メソッドを持ち、前回見たイテレータを置ける場所(つまり、反復可能オブジェクトを置ける場所の大半)にそれを置けるようになっている。このとき、ジェネレータ関数定義の本体はおおよそ__next__メソッドの内容と考えられる。また、ジェネレータ関数により生成されたジェネレータイテレータが、本体に記述したコードの実行を制御するようになっている。

 もう一つ、関数はそれを呼び出すごとに、本体に記述されたコードが一度実行され、return文で戻り値を返すか、あるいは関数末尾まで到達すると、そこで関数の実行が終了する。これに対して、ジェネレータ関数がその呼び出し側に値を返すには、return文ではなく「yield式」を使うところも大きな違いだ。しかも、ジェネレータ関数の本体に書いたコードは、呼び出し側に値を何度も返せる。この辺は文字で説明するよりもコードで見ていただいた方が理解が早いので、まずは簡単なジェネレータ関数を定義して、その動作を見てみよう。

簡単なジェネレータ関数の定義

 簡単なジェネレータ関数の定義を以下に示す。

def simple_generator():
    yield 1
    yield 2
    yield 3

簡単なジェネレータ関数の定義

 既に述べた通り、return文ではなく、「yield 値」という「yield式」が書かれていることが分かる。これが文ではなく、式であることには理由があるが、それについては後で見よう。ここでは、yield式で呼び出し側に値を返せることを覚えておけばよい。そして、上で「呼び出し側に値を何度も返せる」と書いたことに対応するのが、yield式が3つ並んでいる点だ。

 では、上のコードで定義したsimple_generator関数(と、それが返すジェネレータイテレータ)の使い方を見ていく。

 まずは、ジェネレータ関数を呼び出して、ジェネレータイテレータを取得するところから始めよう。

mygeniter = simple_generator()
print(type(mygeniter))  # simple_generator関数の戻り値の型を調べる

print('__iter__' in dir(mygeniter))  # __iter__メソッドがあるか
print('__next__' in dir(mygeniter))  # __next__メソッドがあるか

ジェネレータ関数の呼び出しとジェネレータイテレータの取得

 最初の行では、上で定義したジェネレータ関数を呼び出している。その戻り値であるジェネレータイテレータが変数mygeniterに代入される。2行目では、その型を調べている。3行目と4行目では、ジェネレータイテレータに本当に__iter__メソッドと__next__メソッドがあるかどうかを確認している。

 このコードを実行すると次のようになる。

実行結果 実行結果

 ご覧の通り、simple_generator関数の戻り値の型は「generator」になっている(このことから、ジェネレータイテレータのことを単に「ジェネレータ」と呼ぶことがあるのだろう)。また、その下の出力はいずれもTrueであることから、これがイテレータであることも確認できた。この__next__メソッドから、ジェネレータイテレータのコード(ジェネレータ関数定義の本文として記述したコード)が実行される。

 ジェネレータ関数の戻り値がイテレータであることが分かったので、next関数にこれを渡したり、__next__メソッドを呼び出したりしてみよう。

print(next(mygeniter))  # 次の値を取得
print(mygeniter.__next__())  # 次の値を取得
print(next(mygeniter))  # 次の値を取得
print(mygeniter.__next__())  # 次の値を取得

simple_generator関数の使用例

 これを実行すると、次のようになる。

実行結果 実行結果

 「yield 1」「yield 2」「yield 3」という3つのyield式に渡している整数値が順番に返された後にStopIteration例外が発生している。

 通常の関数では、その定義の本体に書いたコードは関数末尾に到達するか、どこかでreturn文に到達するまで実行が続けられる。これに対して、ジェネレータ関数の本体に書いたコードは、そのジェネレータイテレータの__next__メソッドが呼び出されることで実行される。そして、その実行はyield式に到達するまで続けられ、yield式が実行されると、それに渡された値が呼び出し側に戻され、そこでコードの実行が一時停止されるのだ。その後、再び、ジェネレータイテレータの__next__メソッドが呼び出されると、今度は次の行から実行が再開されて、再びyield式に到達するまで実行が続けられる。このような形で、ジェネレータイテレータのコードは実行されることをまずは覚えておこう。

 上のコードなら、以下のような形でジェネレータ関数に記述したコードは実行されているということだ。

ジェネレータイテレータのコードの実行 ジェネレータイテレータのコードの実行

 このようにしてコードの実行と中断が繰り返され、最終的にyield式に到達しないままコードの実行が終了したところで、StopIteration例外が発生する。コードが実行/中断されている間の情報(ローカル変数の値や、現在どこのコードを実行しているかなど)はジェネレータイテレータ自身が管理してくれるので、プログラマーが気にする必要はない。

 次に、先ほどのsimple_generator関数のコードを次のように変更してみよう。

def simple_generator():
    yield 1
    print('first yield expression done')
    yield 2
    print('second yield expression done')
    yield 3
    print('third yield expression done')

修正したsimple_generator関数

 そして、今度はnext関数(__next__メソッド)を一度だけ呼び出してみよう。

mygeniter = simple_generator()

print(next(mygeniter))  # 次の値を取得

next関数を一度だけ呼び出してみる

 すると、次のような結果が得られる。

実行結果 実行結果

 「first yield expression done」が表示されると思った方もいるかもしれない。しかし、yield式が実行されたところで、実行の制御も呼び出し側に戻るので、直下のprint関数呼び出しは実行されないのだ。そして、もう一度、next関数を呼び出したところで、次の行から実行が再開される。

print(next(mygeniter))  # 次の値を取得

もう一度next関数を呼び出してみる

 今度は最初のyield式の次の行が実行されて、次のyield式まで実行が続けられるので、次のような結果になる。

実行結果 実行結果

 次に前回に見た0から順にカウントアップしていくだけのイテレータを、ジェネレータ関数を使ってジェネレータイテレータとして書き直してみよう。

       1|2 次のページへ

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。