【Python デザインパターン】Iteratorパターン

Pythonで学ぶデザインパターン

Iteratorパターン

Pythonで配列(list,tuple)の要素を順にコンソールに出力する場合を考えます。

index(添え字)を使ってリストの中身を順にスキャンしていく場合はこのように書きます。

char_list = ["a","b","c","d","e","f","g"]
for i in range(len(char_list)):
    print(char_list[i])

配列は要素の集合で、indexを指定することで複数ある要素の中から1つを選び出すことができます。

この選び出す方法をパターン化(抽象化&一般化)したものをIteratorパターンと呼びます。

またIteratorは反復子とも呼ばれます。

サンプルプログラム

0~10の数値をもつ数値の集合Numbersの要素を順に出力するサンプルプログラムを読んでみましょう。

クラス図

iterator

Aggregateクラス(インターフェイス)

class Aggregate:
    def iterator(self):
        return

Aggregateクラスはiteratorメソッドだけを提供します。

このクラス単体では使用せずインターフェイスとして利用します。

Aggregateクラスを実装した集合体用クラスに対応したiteratorを作成するためのものです。

Iteratorクラス(インターフェイス)

class Iterator:
    def hasNext(self):
        return
    def next(self):
        return

Iteratorクラスは基本的に次の要素が存在するか確認するhasNextメソッド(戻り値はbool)と、

次の要素を返すnextメソッドを提供します。

またnextメソッドは次の要素を返すと同時に内部の状態を進める役割も果たします。

Numbersクラス

class Numbers(Aggregate):
    def __init__(self):
        self.numbers = [0,1,2,3,4,5,6,7,8,9,10]
    def iterator(self):
        return NormalIterator(self.numbers)

NumbersクラスはAggregateの実装です。

数値を順に出力するイテレータをiteratorメソッドで作成します。

NormalIteratorクラス

class NormalIterator(Iterator):
    def __init__(self,numbers):
        self.numbers=numbers
        self.index = 0
    def hasNext(self):
        if self.index < len(self.numbers):
            return True
        else :
            return False
    def next(self):
        number = self.numbers[self.index]
        self.index +=1
        return number

NormalIteratorはIteratorの実装です。

数値の集合を先頭から順に進めるイテレータです。

Iteratorクラスで説明したようにnextメソッドでは内部のindexで指定された要素を取得し、

内部の状態を次に進める処理を行っています。

 

サンプルコード全般

class Aggregate:
    def iterator(self):
        return

class NormalIterator(Iterator):
    def __init__(self,numbers):
        self.numbers=numbers
        self.index = 0
    def hasNext(self):
        if self.index < len(self.numbers):
            return True
        else :
            return False
    def next(self):
        number = self.numbers[self.index]
        self.index +=1
        return number

class Numbers(Aggregate):
    def __init__(self):
        self.numbers=[0,1,2,3,4,5,6,7,8,9,10]
    def iterator(self):
        return NormalIterator(self.numbers)

if __name__ == '__main__':
    numbers = Numbers()
    iterator = numbers.iterator()
    while iterator.hasNext():
        print(iterator.next())

実行してみてください。

数値が0~10まで順に出力されるはずです。

このように要素の集合と操作を切り分けることによって柔軟なプログラムを書くことができます。

Iteratorのその他の拡張

Iteratorは要素をどのように操作し取得させるかの機能を提供します。

今回のサンプルでは順に出力するというものでしたが、

下記のように偶数、奇数のIteratorを作成し要素の取得の方法を変えることもできます。

偶数のIterator(OddIterator)

class OddIterator(Iterator):
    def __init__(self,numbers):
        self.numbers=numbers
        self.index = 0
    def hasNext(self):
        if self.index < len(self.numbers):
            return True
        else :
            return False
    def next(self):
        number = self.numbers[self.index]
        self._countup()
        return number

    def _countup(self):
        while True:
            self.index += 1
            if self.index > len(self.numbers) - 1:
                break
            elif self.numbers[self.index] % 2 == 0:
                break

奇数のIterator(EvenIterator)

class EvenIterator(Iterator):
    def __init__(self,numbers):
        self.numbers=numbers
        self.index = 0

    def hasNext(self):
        if self.index < len(self.numbers):
            if self.numbers[self.index] == 0:
                self._countup()
                return self.hasNext()
            return True
        else :
            return False

    def next(self):
        number = self.numbers[self.index]
        self._countup()
        return number

    def _countup(self):
        while True:
            self.index += 1
            if self.index > len(self.numbers) - 1:
                break
            elif self.numbers[self.index] > 0 and self.numbers[self.index] % 2 == 1:
                break

メモ

Iteratorパターンについて書きましたが、

実はPythonにはfor var in list:と書けるように

__iter__や__next__メソッドが定義されています。

https://docs.python.org/ja/3/library/stdtypes.html#typeiter

 

 

 

あわせて読みたい