[Python入門]イテレータとは:Python入門(1/2 ページ)
Pythonにおける複数データの反復処理を支える基盤である反復可能オブジェクトとイテレータの振る舞いを見た後、自分でイテレータを定義してみよう。
Pythonはリストやタプル、その他の言語構造で反復処理の概念をサポートしている。そこで使われるのが「反復可能オブジェクト」や「イテレータ」という概念だ。今回は、反復可能オブジェクトとイテレータの振る舞いを確認して、自分でイテレータを定義してみよう。
イテレータとは
イテレータについては第18回「リストと繰り返し処理」の「イテレータと反復可能オブジェクト」でも簡単に触れた。その内容をまとめると次のようになる。
- イテレータは「データの流れを表現する」オブジェクト
- 反復可能オブジェクトは「要素を一度に1つずつ返せる」オブジェクト
- 反復可能オブジェクトは、それが内包するイテレータを返す__iter__メソッドを持つ
- イテレータは自身を戻り値とする__iter__メソッド、次の要素を返す__next__メソッドを持つ
リストやタプル、辞書、集合は典型的な反復可能オブジェクトだ。これらは内部にイテレータを持ち、for文などで要素を反復的に処理する際には、Python内部で自動的にそれが使われるようになっている。
なお、反復可能オブジェクトの条件はイテレータを戻り値とする__iter__メソッドを持つことと、イテレータは自身を戻り値とする__iter__メソッドを持つことから分かるように、イテレータは反復可能オブジェクトでもある。よって、Pythonのコードの中で反復可能オブジェクトを受け取る部分にはイテレータを記述できる。
ここではrange関数を呼び出すと作られるrangeクラスのオブジェクトを例に取ろう。本連載でも既に何度か見てきたが、range関数はパラメーターにスタート値(デフォルト値は0)、ストップ値、ステップ値(デフォルト値は1)を取り、スタート値からストップ値までの整数値がステップ値ごとに並んだシーケンスを表すオブジェクトを生成する(ストップ値は実際にはrangeクラスのオブジェクトが表す範囲には含まれない)。
以下はストップ値に3を指定したrange関数を呼び出すコードだ(スタート値は0、ステップ値は1となる)。
r = range(3)
print(type(r))
これを実行すると、次のように変数rにはrangeクラスのオブジェクトが代入されていることが分かる。
このオブジェクトに先ほど述べた__iter__メソッドと__next__メソッドがあるかを調べてみよう。
print('__iter__' in dir(r)) # Trueならrangeオブジェクトは__iter__メソッドを持つ
print('__next__' in dir(r)) # Trueならrangeオブジェクトは__next__メソッドを持つ
これを実行すると、次のような結果になる。
この結果から、rangeクラスのオブジェクトには__iter__メソッドはあるが、__next__メソッドはないことが分かる。つまり、これは反復可能オブジェクトだが、イテレータではないということだ。
イテレータを取り出すには、__iter__メソッドを呼び出すか、組み込みのiter関数に反復可能オブジェクトを渡す。iter関数に反復可能オブジェクトを渡すと、そこから__iter__メソッドが呼び出されてイテレータが返されるようになっている。そのため、下の2行は同じことになる。
range_iter = r.__iter__() # イテレータを取り出す
range_iter2 = iter(r) # イテレータを取り出す
print(type(range_iter))
print(type(range_iter2))
実行結果を以下に示す。
__iter__メソッド(や__next__メソッド)などの特殊メソッドは、通常、それに対応するPythonの構文(組み込み関数や角かっこ「[]」によるインデックスやスライスの指定、あるいは何らかの文など)が存在しており、それらの構文と特定のクラスのオブジェクトを組み合わせることで、そのクラスのメソッドが自動的に呼び出されるようになっている。
上のようにして、取り出したイテレータは「range_iterator」クラスのオブジェクトであり、これがrangeオブジェクトが表す整数列の流れ(0、1、2、……)を表し、その__next__メソッド(または対応する組み込みのnext関数)を繰り返し呼び出していくことで、整数を一つ一つ順番に取り出せるようになっている。
以下に例を示す。ここでは上で作成した2つのイテレータを使って、__next__メソッドとnext関数を呼び出している。
print(range_iter.__next__()) # range_iterから次の値を取得
print(range_iter.__next__()) # range_iterから次の値を取得
print(next(range_iter2)) # range_iter2から次の値を取得
print(next(range_iter2)) # range_iter2から次の値を取得
これを実行すると次のようになる。
どちらも0と1が順に出力されているのは、それぞれのイテレータは整数列の流れを独自に管理しているからだ。元のrangeオブジェクトは1つだけだが、そこから別々のイテレータを生成すれば、それらは元のrangeオブジェクトの要素の流れを別々に管理する。
また、Pythonのドキュメント「iterator.__next__()」を見ると、__next__メソッドは「コンテナの次のアイテムを返します」とあるが、ここで見たように、__next__メソッドはイテレータが管理する要素を最初から最後まで順番に返送するものだと考えるようにしよう。
既に試したように、元のrangeオブジェクトは反復可能オブジェクトだが、イテレータではない。そのため、次のような呼び出しはできない。
next(r)
上のコードを実行すると、次のように例外が発生する。
この実行結果を見ると、「rangeオブジェクトはイテレータではない」と明確なメッセージが表示されている。__next__メソッドを呼び出そうとすると、どうなるかはご自分で試してみてほしい。
なお、イテレータで列挙する要素が尽きると、それ以降の__next__メソッド/next関数呼び出しはStopIteration例外を発生する。今扱っているrangeオブジェクトは「range(3)」として作成し、上のコードでそのうちの2つの要素を使ったところだ。つまり、残った要素はあと1つ。ここで以下のコードを実行してみよう。
print(next(range_iter)) # range(3)で作成したので、要素はここで尽きる
print(next(range_iter)) # これ以降は例外が発生する
これを実行すると、次のようになる。前述の通り、要素を使い果たした後はStopIteration例外が発生したことが分かるはずだ。
ここまでに見てきたイテレータの性質を以下にまとめる。
- 自分自身を戻り値とする__iter__メソッドを持つ
- 自分が管理している要素列の値を1つずつ返す__next__メソッドを持つ
- __next__メソッドでは、要素が尽きたらStopIteration例外を発生する
これらの性質を満たした簡単なイテレータを次に定義してみよう。
Copyright© Digital Advantage Corp. All Rights Reserved.