Update 2023.11.18 2017.05.05

Python デザインパターン サンプルコード Iterator
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」

◆◆Iterator パターンの使われる場面◆◆

いわゆるコンテナオブジェクトから要素を順番に取り出すのがIterator パターンです。オブジェクト指向の考えにより,コンテナの構造をクライアントから隠蔽することも目的の一つです。


(2023-11-18)Python3.11で動作確認済み


◆◆Iterator パターンとは◆◆

GoFによれば,Iterator パターンの目的は, 「集約オブジェクトが基にある内部表現を公開せずに,その要素に順にアクセスする方法を提供する。」

GoFによれば,Iterator パターンの別名は,Cursor パターンである。

GoFによれば,次のような場合に Iterator パターンを使うとよい。
・コンテナオブジェクトの内部表現を公開せずに,その中にあるオブジェクトにアクセスしたい場合。
・コンテナオオブジェクトに対して,複数の走査をサポートしたい場合。
・異なるコンテナオ構造の走査に対して,単一のインタフェースを提供したい(すなわち,ポリモルフィックな iteration をサポートしたい)場合。

GoFによれば,Iterator パターンの関連するパターンは次のようなものである。
Composite パターン:iterator は,しばしば Composite のような再帰的な構造に対して適用される。
Factory Method パターン:ポリモルフィックな iterator では,Iterator のサブクラスの中から適切なクラスをインスタンス化するために,factory method を使う。
Memento パターン:しばしば Iterator パターンとともに使われる。iterator は,iteration の状態を把握するために memento を使うことができる。この場合,iterator は memento を内部に保持する。

◆◆Iterator パターンのサンプルのクラス構成◆◆

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。

サンプルの内容は本棚の本を順番に取り出すというものです。イメージとしては蔵書の一覧表を1つづつ取り出して見せるのに近いです。

Java サンプルでは,リストをつくる class Book があり,一覧表がリストであることを隠蔽しています。オブジェクト指向の考えからして,たいへん良いことなのですが,クライアント;メインルーチンのやることの本質ではないので,Pythonサンプルから省略させていただきました。

クラス構成を考える上で重要なことは,Python には Iterator 機能があり,Java には Iterator 機能がないということです。だからこそデザインパターンとして提案されたのでしょう。Python での Iterator の使い方は次のようなものです。順番に取り出すには組込関数の next() を使います。次のようにインスタンス化して使います。最後に例外が出力されます。

これらの Iterator 機能が for ループにそっくり組込まれています。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# これがコンテナです
bookshelf = ["A", "B", "C"]
# 最も簡単な Iterator です
for book in bookshelf:
    print(book)
# オブジェクト指向ではインスタンス化します
it = iter(bookshelf)
for book in it:
    print(book)
# これがオブジェクトの原始的な使い方です
it = iter(bookshelf)
print(next(it))
print(next(it))
print(next(it))
print(next(it))
"""標準出力
A
B
C
A
B
C
A
B
C
Stop iteration
"""

オブジェクト指向では上のように操作対象を直接いじくりまわすようなことはしません。クラスをオブジェクトにして決められたインターフェイスを使ってそれを呼びます。

Python にはイテレータ機能があるのでそれを実装すれば何が足りないか教えてくれます。
とりあえず,for ループを実装します。

class BookShelf(object):
    pass

if __name__ == '__main__':
    for book in BookShelf():
        print(book)

"""標準出力
TypeError: 'BookShelf' object is not iterable
"""

エラーメッセージに iterable ではないとあります。
iterable とは__iter__メソッドを持つオブジェクトのことです。このメソッドで next メソッドを持つオブジェクト(こっちが iterator)を戻す決まりです。
そのように実装します。

class BookShelf(object):
    def __iter__(self):
        return BookShelfIterator()

class BookShelfIterator(object):
    def __init__(self):
        pass

if __name__ == '__main__':
    for book in BookShelf():
        print(book)
"""標準出力
TypeError: iter() returned non-iterator of type 'BookShelfIterator'
"""

エラーメッセージに iterator がないとあります。
iterator には next メソッドを実装する決まりになっています。
そのように実装します。

class BookShelf(object):
    def __iter__(self):
        return BookShelfIterator()

class BookShelfIterator(object):
    def __init__(self):
        self.i = 3
    def __next__(self):
        if self.i > 0:
            self.i -= 1
            return "next done"
        raise StopIteration()

if __name__ == '__main__':
    for book in BookShelf():
        print(book)
"""標準出力
next done
next done
next done
"""

この実装で骨格は完成しました。これが Iterator パターンのクラス構成です。

◆◆Iterator パターンの実装◆◆

Java には言語仕様としてイテレータ―はありませんが,java.util(ユーティリティクラス)に Iterator があります。つまり,オブジェクト指向によりこれらのメソッドを再定義してオーバーライドしているわけです。Python サンブルでも同様です。

class BookShelf は iterable,class BookShelfIterator は iterator と言います。Python3 では collections.abc(抽象基底クラスの一部)のサブクラスとして実装します。

Java では for ループではなく while を使います。理由はメソッド hasNext() と next() を使うからです。使い方はつぎの Java の実装を見れば判ります。これは Iterator 機能を持たない言語の定番であり,オブジェクト指向の目的に合うものです(Iterator 機能を持たない言語をオブジェクト指向言語ではないという人もいます)。それらの機能は,
  hasNext():次の要素の有無を確認する
  next():次の要素を取り出す
であり,コンテナオブジェクトの構造を隠蔽しているインターフェイスになっているのが理解できます。

(Java のイテレータの呼び出し)
Iterator it = bookShelf.iterator();
while (it.hasNext()) {
    Book book = (Book)it.next();
    System.out.println(book.getName()); //getName は class Book のメソッドです

(Python のイテレータの呼び出し)
it = BookShelf(books)
for book in it:
    print(book)

Python の場合,イテレータの機能として,for ループの in のオブジェクトの iter メソッドが呼ばれ,それが戻すオブジェクトの next メソッドが呼ばれます。それらは明示されなくてもそのように動作します。Python サンプルではこれらをすべて明示して実装します。

next メソッドにリストの要素を1つづつ取り出す機能を実装します。いわゆるカーソルをインクリメントする方式です。難しいことはないので説明は省略します。

◆Iterator パターンを1つのクラスで実装する例

Iterator パターンを1つのクラスで実装する例は Webにたくさんあります。iterator クラスが iterable クラスじしんになっているので,iter メソッドが戻すオブジェクトが self になります(return selfとなる)。 この実装は,next 操作とインスタンス化の関係で変な動きをするかもしれません。

◆◆ソースコード◆◆

このWebページの左上隅からダウンロードできます。

ソースファイルは1つです。
・Iterator.py;Iterator パターンのPythonサンプル

【Iterator.py】
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import collections.abc

class BookShelf(collections.abc.Iterable):
    def __init__(self, books):
        self.books = books
    def __iter__(self):
        return BookShelfIterator(self.books)

class BookShelfIterator(collections.abc.Iterator):
    def __init__(self, books):
        self.books = books
        self.length = len(books)
        self.index = 0
    def __next__(self):
        if self.index == self.length:
            raise StopIteration
        book = self.books[self.index]
        self.index += 1
        return book

def main():
    books = ["Around the World in 80 Days",
             "Bible",
             "Cinderella",
             "Daddy-Long-Legs"]
    it = BookShelf(books)
    for book in it:
        print(book)

if __name__ == "__main__":
    main()
"""標準出力
Around the World in 80 Days
Bible
Cinderella
Daddy-Long-Legs
"""

以上

トップページに戻る
inserted by FC2 system