180 likes | 314 Views
Iterator を使おう. 日本 Python ユーザ会 石本 敦夫. Iterator パターン. GoF による定義 集約オブジェクトが基にある内部表現を公開せずに、その要素に順にアクセスする方法を提供する. Iterator. Collection. Item. Python のイテレータ. データやアルゴリズムをイテレータでラップし、シーケンスのように順にデータを取得する方法を提供する. Iterator. Collection. Function. File. etc,. Python のイテレータ サポート.
E N D
Lightweight Language Weekend ls-lRシェル Iteratorを使おう 日本Pythonユーザ会 石本 敦夫
Lightweight Language Weekend ls-lRシェル Iterator パターン • GoFによる定義 集約オブジェクトが基にある内部表現を公開せずに、その要素に順にアクセスする方法を提供する Iterator Collection Item
Lightweight Language Weekend ls-lRシェル Pythonのイテレータ • データやアルゴリズムをイテレータでラップし、シーケンスのように順にデータを取得する方法を提供する Iterator Collection Function File etc,...
Lightweight Language Weekend ls-lRシェル Pythonのイテレータ サポート • Pythonのステートメントはイテレータを使用するように変更されている
Lightweight Language Weekend ls-lRシェル イテレータインターフェース • Iterable - イテレート可能オブジェクト • 組み込み関数 iter()を使ってイテレータを取得することができるオブジェクト • for文などで直接使用することができる • Python組み込みのコンテナはすべてIterable • イテレータを返すメソッド__iter__() を持つ • iterator – イテレータオブジェクト • 次の値を返すnext()メソッドを持つ • next()で返すオブジェクトがなければ、StopIteration例外を送出する • __iter__()メソッドを持ち、自分自身を返す
Lightweight Language Weekend ls-lRシェル イテレータの例 • Fileイテレータ - ファイルを行のシーケンスとみなし、順に読み込む inFile = open("./foo.txt") for line in inFile: print line # 又は inFile = open("./foo.txt") fileIter = iter(inFile) print fileIter.next() print fileIter.next()
Lightweight Language Weekend ls-lRシェル イテレータの実装例 class AlterIter: '''seq1, seq2内の要素を交互に返すイテレータ''' def __init__(self, seq1, seq2): self._seqs = (seq1, seq2) self._cur = 0 self._max = min(len(seq1), len(seq2)) # 最大インデックス値 def __iter__(self): return self def next(self): n, idx = self._cur % 2, self._cur//2 if idx >= self._max: raise StopIteration # iterate終了 ret = self._seqs[n][idx] self._cur += 1 return ret • 通常、__init__(), __iter__(), next()は最低限必要 • 次回の呼び出しに備えて状態を保存しなければならない
Lightweight Language Weekend ls-lRシェル AlterIterの使用方法 • for ステートメントでイテレータを使用する for v in AlterIter([1,2],['a','b']): print v, 出力 => 1 a 2 b • もちろん、next()メソッドで順次読み出しても良い values = AlterIter([1,2],['a','b']) print values.next(), print values.next(), print values.next(), print values.next(), 出力 => 1 a 2 b
Lightweight Language Weekend ls-lRシェル イテレータのメリット • 実際にシーケンスを作成する必要がないため効率が良い • 無限長のシーケンスを扱う事ができる • 呼び出し元の処理がシンプルになる • データを取り出すタイミングを呼び出し元で制御できる • forループ内で終了条件をチェックする必要がないため、処理がすっきりする
Lightweight Language Weekend ls-lRシェル Generator関数 • 関数定義の形でイテレータを定義 • Generator関数は戻り値としてイテレータを返す • イテレータのnext()は、yield文で指定した値を次々に返す • # return ('最初', '2番目', '終わり' )と同じ • def gen(): • yield '最初' • yield '2番目' • yield '終わり' • # Generetor関数 の呼び出し • v = gen() • print v.next(), # '最初' • print v.next(), # '2番目' • print v.next(), # '終わり' • # 又は • for v in gen(): • print v, • # 出力 => 最初 2番目 終わり
Lightweight Language Weekend ls-lRシェル Generatorの動作 • 呼び出されると イテレータの一種であるgeneratorオブジェクトを返す • generatorのnext()メソッドを呼び出すと、関数のyield文までを実行してその値を返す • yield文に遭遇せずに関数が終了すると、StopIteration例外を送出する class gen: def __init__(self): self._n = 0 def __iter__(self): return self def next(self): if self._n == 0: return 'A' if self._n == 1: return 'B' raise StopIteration def gen(): yield 'a' yield 'b' 同じ
Lightweight Language Weekend ls-lRシェル Generatorのメリット • 簡単にイテレータを作成する事ができる • 処理状態をどこかに退避する必要がない • 速い! • クラスを使ったイテレータより100%以上速いケースも • ほとんどローカル変数のみで実行できる • 状態を退避する必要がない
Lightweight Language Weekend ls-lRシェル Generator版AlterIter def AlterIter(seq1, seq2): _max = min(len(seq1), len(seq2)) for idx in range(_max): yield seq1[idx] yield seq2[idx] • イテレータクラスより簡単 • 高速!
Lightweight Language Weekend ls-lRシェル Fibonacci数列 def fibonacci(): i = j = 1 yield i yield j while True: i, j = j, i+j yield j for n in fibonacci(): print n, 出力 => 1 1 2 3 5 8 13 21 34 55 89 ...
Lightweight Language Weekend ls-lRシェル イテレータの合成 def odds(iterable): '''奇数のみを返すイテレータ''' for v in iterable: if v % 2: yield v def lessThan(iterable, cond): '''cond以下の値のみを返すイテレータ''' for v in iterable: if v < cond: yield v seq = [90, 22, 49, 93, 49, 68, 36, 96, 31, 23] for v in odds(lessThan(seq, 50)): print v,
Lightweight Language Weekend ls-lRシェル パフォーマンス比較 def test1(seq): for v in lessThan(odds(seq), 50): pass def test2(seq): for v in seq: if (v % 2) and (v < 50): pass def test3(seq, f): for v in seq: if f(v): pass # テスト用シーケンス seq = [random.randint(0, 10000) for v in range(1000000)] test1(seq) test2(seq) test3(seq, lambda v: (v < 50) and (v % 2)) 測定環境: Python2.3.3 Windows2000 結果 • イテレータの二段重ねでもそれほど遅くはならない • 柔軟性とのトレードオフ
Lightweight Language Weekend ls-lRシェル 参考リンク • PEP 234 Iterators http://www.python.org/peps/pep-0234.html • PEP 255 Simple Generators http://www.python.org/peps/pep-0255.html
Lightweight Language Weekend ls-lRシェル 終わりに • イテレータを使おう。イテレータはPythonicだ。 • Generatorを使おう。Generatorは魔法じゃない。