標準形
Pythonの特殊メソッド__iter__
と__next__
を使ってオブジェクトのイテレーターをつくることができる。ジェネレーターがyield文によって任意の値を生成するのに対して、イテレーターはコレクションの要素を順次取り出すときなどに有用。
__iter__
と__next__
の2つのメソッドを持ったオブジェクトがfor文で参照されるたびに、以下のように動作する。
- forを始める前に
__iter__
メソッドが実行される - forブロックのループのたびに__next__メソッドが実行される
2つのメソッドの書き方は以下の通り。
- __iter__
- 戻り値としてオブジェクト自身(self)を返す。
- __next__
- イテレートされるべきプロパティを変更し、戻り値として現在値を返す。もし終了条件に合致しいている場合、イテレート終了の例外を発する。
次の例では、1から初めて最初に設定した値まで1ずつ増える数列のイテレーターをつくっている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Seq: def __init__(self, max): self._max = max def __iter__(self): self._a = 1 return self def __next__(self): result = self._a if result > self._max: raise StopIteration self._a += 1 return result seq = Seq(10) for n in seq: print(n, end=" ") print() for n in seq: print(n, end=" ") # 1 2 3 4 5 6 7 8 9 10 # 1 2 3 4 5 6 7 8 9 10 |
最初の__init__
メソッドでは引数から数列の最大値をプロパティにセット。
__iter__
メソッドはカウンタになるプロパティに初期値をセットして、selfを返す。
__next__
メソッドはそれが呼ばれるたびに次のことを行う。
- カウンタ値が最大値を超えていたらforブロックから抜け出るよう例外を返す
- 現在のカウンタ値を返す
- カウンタ値をインクリメントする
forブロックから抜け出るための例外はStopIteration
が準備されていて、forブロックがこの例外を受け取ると、エラーを発生させることなくforブロックの次の処理に移る。
インスタンス変数のイテレーターの取得
たとえばあるクラス内にリストがあって、リストそのものの構造は安全に守ったまま、その要素にアクセスする方法を考えてみた。
インスタンス変数のリストそのものやgetterなどで取得したリストへの参照をメソッドで返すと、リストそのものの操作までできてしまうが、それを防ぎたい。
そこで、インスタンス変数のイテレーターを生成するクラスを元のクラスのインナークラスとして準備して、外部へはイテレーターのインスタンスを渡すようにしてみた。
インナークラスでなくてもできるが、元のクラスのインスタンス変数の構造と密接に関連しているなら、インナークラスでまとめた方が明快だと思う。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
class MainClass: # リストのイテレータを生成するクラス class Iterator: def __init__(self, lst): self.lst = lst self.count = -1 self.max = len(lst) def __iter__(self): return self def __next__(self): self.count += 1 if self.count == self.max: raise StopIteration return self.lst[self.count] # 元のクラスは2つのリストを配列として持つ def __init__(self): self.lst = [] self.lst.append([1, 2, 3, 4]) self.lst.append(['A', 'B', 'C', 'D']) # 引数の番号でリストを指定してイテレータを取得 def get_iterator_of_list(self, n): return MainClass.Iterator(self.lst[n]) obj = MainClass() for n in obj.get_iterator_of_list(0): print(n, end=' ') print() for n in obj.get_iterator_of_list(1): print(n, end=' ') # 1 2 3 4 # A B C D |