[Python入門]ジェネレータ関数とジェネレータイテレータの基礎:Python入門(2/2 ページ)
イテレータを戻り値とする「ジェネレータ関数」、それが返す「ジェネレータイテレータ」の使い方の基本について説明する。
カウントアップするジェネレータイテレータ
クラスを使って定義したカウントアップするだけのイテレータは次のようなものだった。
class CountUpIterator:
def __init__(self, limit=5):
self.limit = limit
self.counter = -1
def __iter__(self):
print('__iter__ method called')
return self
def __next__(self):
print('__next__ method called')
self.counter += 1
if self.counter >= self.limit:
raise StopIteration()
return self.counter
ここでは__init__メソッドでカウンターの上限値を指定して、__next__メソッドで現在値と上限値を比較して、上限に達していなければ、現在値を返して、その値を1増やすようにしていた。上限値の指定は、ジェネレータ関数のパラメーターに受け取ることにすれば、ジェネレータ関数は次のように書ける。クラスを使ったときよりもシンプルに、その動作だけを書けていることに注目しよう。
def countup_geniter(limit=5):
counter = -1
while True:
counter += 1
if counter >= limit:
break
yield counter
CountUpIteratorクラスでは、__next__メソッドは呼び出されるたびに、そのコードが実行されるので、現在値(self.counter)と上限値(self.limit)をインスタンス変数として管理していた。一方、ジェネレータ関数では、それらをローカル変数やパラメーターを使って現在値(counter)と上限値(limit)を管理して、それを無限ループ(「while True:」のブロック)の中で操作するようにしている。
無限ループの中でyield式が実行されると、現在値(と実行の流れ)が呼び出し側に戻され、次のnext関数(__next__メソッド)呼び出しにより、yield式の次の行、つまり次の無限ループの先頭に移り、次のループが始まる。これにより、上限に到達するまでは、ジェネレータイテレータが連続的に値を生成(ジェネレート)するようになっている。
現在値と上限値を比較したときに現在値が上限値に達していれば、クラスベースのCountUpIteratorでは「raise StopIteration()」として自ら例外を発生させていたが、上のコードではbreak文で無限ループを終了している(あるいは、「return」とだけ書いて、関数本体の実行を終了してもよい)。
これは、先ほども述べたように「yield式に到達しないままコードの実行が終了したところで、StopIteration例外が発生する」からだ。自分でStopIteration例外を発生させる必要はない点には注意しよう。上のコードをbreak文から「raise StopIteration()」に変更すると、環境によっては次のようにRuntimeError例外が発生する(Python 3.7.3以降でのデフォルトの振る舞い。それ以前のバージョンでも警告メッセージが表示されるかもしれない。詳細はPEP 479「Change StopIteration handling inside generators」を参照されたい)。
使用例を以下に示す。
for num in countup_geniter(3):
print(num)
ジェネレータイテレータはもちろんイテレータなので、このようにfor文でも利用可能だ。実行結果を以下に示す。
なお、上のコードではクラスを使ったイテレータと似たロジックをジェネレータ関数の本体に書いているが、これは次のようにも書ける。
def countup_geniter(limit=5):
counter = 0
while True:
if counter >= limit:
break
yield counter
counter += 1
ここでは現在値を表す変数counterの初期値を「0」として、その値を1つ増やす行を本体の最下部に移している。このように書けるのは、ジェネレータイテレータでは、yield式で制御が呼び出し側に戻った後も、その内部でコードのどこが実行されているかが管理されていて、次にnext関数(__next__メソッド)が呼び出されて、制御がジェネレータイテレータに戻ったときには、yield式の次の行(ジェネレータ関数定義の本体の最終行にある「counter += 1」)が実行された後に、次のループが始まるようになるからだ。クラスベースのCountUpIteratorの__next__メソッドでは「return self.counter」行の後に「self.counter += 1」と書いても、その行が実行されることはないので、これと全く同じ書き方はできないことに気を付けよう。
yield式の値とsendメソッド
ところで、先ほど、「yield 値」は「yield式」だと述べた(これは「return 値」が「return文」であるのと対照的だ)。これは、「yield 値」が戻り値を持つからだ(なお、式は文でもあるので、単純に「yield 値」と書いた場合、それはyield文でもある)。
yield式の値は、ジェネレータイテレータがどのような形で実行を再開するかによる。今までに見てきたnext関数(ジェネレータイテレータの__next__メソッド)から実行が再開された場合、その値はNoneとなる。式なので、その値は代入演算子を使って任意の変数に代入できる。
Copyright© Digital Advantage Corp. All Rights Reserved.