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

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

[かわさきしんじ,Deep Insider編集部]
前のページへ 1|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となる。式なので、その値は代入演算子を使って任意の変数に代入できる。

前のページへ 1|2       

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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