検索
連載

[Python入門]イテレータとはPython入門(2/2 ページ)

Pythonにおける複数データの反復処理を支える基盤である反復可能オブジェクトとイテレータの振る舞いを見た後、自分でイテレータを定義してみよう。

PC用表示 関連情報
Share
Tweet
LINE
Hatena
前のページへ |       

カウントアップするイテレータを定義する

 ここでは単純にカウントアップ(0から指定された範囲の整数値を順に返送)するイテレータを定義してみよう。これはスタート値やステップ値の指定がない、簡易的なrangeクラスのようなものと考えられる。インスタンス生成時に上限を指定すると(デフォルトは5とする)、上限に指定された回数だけ整数値を列挙した後はStopIteration例外を発生するようにしよう。

 ここで定義するクラスはおおよそ次のようなものになる。

class CountUpIterator:
    def __init__(self, limit=5):
        # 上限とカウンターの初期化処理
        pass
    def __iter__(self):
        # 自身を呼び出し側に戻す
        pass
    def __next__(self):
        # 上限を超えるまではカウントアップして、その値を戻す
        pass

カウントアップするイテレータの概形

 このクラスのインスタンスが管理するのは、上限値と現在値だ。上限値はインスタンス生成時に__init__メソッドのパラメーターlimitに渡されるようにする(デフォルト引数値を「5」に指定)。現在値とはnext関数または__next__メソッドを呼び出した側に返送する値のことだ。上限値はインスタンス変数のself.limitで、現在値は同じくインスタンス変数のself.counterで管理するようにしよう。この上限値と現在値を__init__メソッドで初期設定すればよい。

 また、__iter__メソッドは自分自身(self)を戻り値にするだけなので「return self」の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):
        # 上限を超えるまではカウントアップして、その値を戻す
        pass

カウントアップするイテレータの__init__メソッドと__iter__メソッドを実装したところ

 __init__メソッドで「self.counter = -1」と初期値を「-1」にしているのは、__next__メソッドで値を返すときの処理を簡単にするためだ。また、__iter__メソッドには呼び出されたことを画面に表示するprint関数呼び出しも追加しておいた。

 __next__メソッドでやることは、self.counterの値を1つ増やすこと、self.counterの値とself.limitの値を比較して、上限に達していればStopIteration例外を発生させること、そうでなければself.counterの値を返送することだ。これをコードにすると次のようになる。

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__メソッドでself.counterの値を-1にしておいたので、その値を1つ増やすだけで、最初に返送する「0」という値にできる。__init__メソッドでself.counterの値を0にした場合にどんなコードを書けばよいかは自分で考えてみよう。

 では、このコードでCountUpIteratorクラスを定義したら、その振る舞いを確認していこう。

 まずは以下のコードを実行してみる。

countup_iter = CountUpIterator(3)
countup_iter2 = iter(countup_iter)
print(countup_iter is countup_iter2)  # True

CountUpIteratorクラスのインスタンスを作成し、iter関数でそこからイテレータを取り出す

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

実行結果
実行結果

 「__iter__ method called」と表示されていることから、iter関数を呼び出すと、__iter__メソッドが呼び出されることが確認できた。また、__iter__メソッドは自分自身を戻り値とするので、「iter(countup_iter)」の戻り値はcountup_iter自身だ。よって、その後の「countup_iter is countup_iter2」の値はTrueとなる。これは反復可能オブジェクトから複数のイテレータを取り出したときに、それらが別々のオブジェクトとなるのとは対照的だ*1

*1 実際には反復可能オブジェクトの__iter__メソッドを呼び出したときにどういう処理が行われるかはコード次第なので、確実にそうなるとは筆者には断言できない。ただし、Pythonの用語集のiterator項では「コンテナオブジェクトは、自身を iter() 関数にオブジェクトに渡したり for ループ内で使うたびに、新たな未使用のイテレータを生成します」とあるので、少なくともPythonの組み込み型に関していえば、反復可能オブジェクトからイテレータを取り出すときには新規に作られたものが返されると考えてよいだろう(実際、反復可能オブジェクトからイテレータを取り出そうというときに、どこかで使用中のイテレータを返されるのではなく、新規にイテレータが作られる方が、多くの場合、使う側の意図に沿った振る舞いだといえるだろう)。


 次に以下のコードを実行してみよう。上のコードでは「CountUpIter(3)」として、このクラスのインスタンスを生成している。つまり、上限値は「3」だ。そこで、以下のコードでは__next__メソッドを都合4回呼び出している。

print(next(countup_iter))
print(countup_iter2.__next__())
print(next(countup_iter2))
print(countup_iter.__next__())

CountUpIteratorクラスの利用例

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

実行結果
実行結果

 まず、値を出力するたびに「__next__ method called」と表示されているので、next関数からも最終的には__next__メソッドが呼び出されているのが確認できた。次に、上限値を超えたら、StopIteration例外がきちんと発生していることも上の画面からは分かる。これらから、Pythonで一般にイテレータと呼ばれているものと同様な処理ができているといってよいだろう。

 なお、このクラスを少し変更するだけで、ランダムな整数値(0〜100の範囲)を返送するイテレータも定義できる。参考までに以下にコードを示す。

from random import randint

class RandomIntIterator:
    def __init__(self, limit=5):
        self.limit = limit
        self.counter = -1
    def __iter__(self):
        return self
    def __next__(self):
        self.counter += 1
        if self.counter >= self.limit:
            raise StopIteration()
        return randint(0, 100)

乱数を反復するRandomIntIteratorクラス

 このRandomIntIteratorクラスを定義して、今度は次のコードを試してみよう。

mylist = list(RandomIntIterator())
print(mylist)

RandomIntIteratorクラスの使用例

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

実行結果
実行結果

 反復可能オブジェクトを必要とする多くの場所では、イテレータを置くこともできる。そのため、上のコードではlist関数に反復可能オブジェクトの代わりにRandomIntIteratorクラスのインスタンスを渡している。これにより5個のランダムな整数値を要素とするリストが定義されたというわけだ。

 for文でも同様だ。for文では「in」に続けて反復可能オブジェクトを置くが、ここにイテレータを置いてもよい。

for num in RandomIntIterator(3):
    print(num)

for文で反復可能オブジェクトの代わりにイテレータを使用する例

 Pythonのドキュメント「for文」には「in」に続けて記述する式の「結果はイテラブルオブジェクト(反復可能オブジェクト)にならなければなりません」とある。また、その「結果に対するイテレータが生成されます」ともある。そして、反復可能オブジェクトから(iter関数と同様な仕組みを用いて)イテレータが取り出されるのであれば、イテレータ自身を戻り値とするイテレータもそこに置けるというわけだ。ただし、既に見たように反復可能オブジェクトからイテレータを取り出すときと、イテレータからイテレータを取り出すときではその動作が異なる。

 イテレータをクラスレベルで定義しようとすると、今回見たようなコードになる。だが、「ジェネレータ」という仕組みを使うと、def文を使ってイテレータを戻り値とする関数を定義することも可能だ。これについては次回見ていくことにしよう。

まとめ

 今回は本連載で何度か登場していたイテレータについて、実際に定義をしながら、それがどんなものかを見た。次回は、イテレータをより簡便に定義可能なジェネレータについて見ていくことにしよう。

今回のまとめ

  • イテレータは「データの流れを表現する」オブジェクト
  • 反復可能オブジェクトは「要素を一度に1つずつ返せる」オブジェクト
  • 反復可能オブジェクトは、それが内包するイテレータを返す__iter__メソッドを持つ
  • イテレータは自身を戻り値とする__iter__メソッド、次の要素を返す__next__メソッドを持つ
  • Pythonのコード中で、反復可能オブジェクトを置ける場所の多くでは、イテレータを代わりに置いてもよい
  • 反復可能オブジェクトからイテレータを取り出すときには、新規にイテレータが作成される
  • イテレータからイテレータを取り出すときには、元のイテレータが取り出される

Copyright© Digital Advantage Corp. All Rights Reserved.

前のページへ |       
[an error occurred while processing this directive]
ページトップに戻る