PowerPoint プレゼンテーション

Lightweight Language
Weekend ls-lRシェル
Iteratorを使おう
日本Pythonユーザ会
石本 敦夫
Copyright c 2004 Python Japan User's Group.
Lightweight Language
Weekend ls-lRシェル
Iterator パターン
• GoFによる定義
集約オブジェクトが基にある内部表現を公開せず
に、その要素に順にアクセスする方法を提供する
Iterator
Collection
Item
Copyright c 2004 Python Japan User's Group.
Lightweight Language
Weekend ls-lRシェル
Pythonのイテレータ
• データやアルゴリズムをイテレータでラップし、
シーケンスのように順にデータを取得する方
法を提供する
Iterator
Collection
Function
File
etc,...
Copyright c 2004 Python Japan User's Group.
Lightweight Language
Weekend ls-lRシェル
Pythonのイテレータ サポート
• Pythonのステートメントはイテレータを使用するよう
に変更されている
Python2.1以前
Python2.2以降
for i in [1,2,3]:
print i
seq = [1,2,3]
n=0
while True:
try:
print seq[n]
except IndexError:
break
n += 1
iterator = [1,2,3].__iter__()
while True:
try:
i = iterator.next()
except StopIteration:
break
print i
i, j = (1,2)
seq = (1,2)
i = seq[0]
j = seq[1]
iterator = (1,2).__iter__()
i = iterator.next()
j = iterator.next()
Copyright c 2004 Python Japan User's Group.
Lightweight Language
Weekend ls-lRシェル
イテレータインターフェース
• Iterable - イテレート可能オブジェクト
– 組み込み関数 iter()を使ってイテレータを取得することが
できるオブジェクト
– for文などで直接使用することができる
– Python組み込みのコンテナはすべてIterable
– イテレータを返すメソッド__iter__() を持つ
• iterator – イテレータオブジェクト
– 次の値を返すnext()メソッドを持つ
– next()で返すオブジェクトがなければ、StopIteration例
外を送出する
– __iter__()メソッドを持ち、自分自身を返す
Copyright c 2004 Python Japan User's Group.
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()
Copyright c 2004 Python Japan User's Group.
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]
• 通常、__init__(), __iter__(), next()は
self._cur += 1
最低限必要
return ret
• 次回の呼び出しに備えて状態を保存し
なければならない
Copyright c 2004 Python Japan User's Group.
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
Copyright c 2004 Python Japan User's Group.
Lightweight Language
Weekend ls-lRシェル
イテレータのメリット
• 実際にシーケンスを作成する必要がないため
効率が良い
• 無限長のシーケンスを扱う事ができる
• 呼び出し元の処理がシンプルになる
– データを取り出すタイミングを呼び出し元で制御
できる
– forループ内で終了条件をチェックする必要がな
いため、処理がすっきりする
Copyright c 2004 Python Japan User's Group.
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番目 終わり
Copyright c 2004 Python Japan User's Group.
Lightweight Language
Weekend ls-lRシェル
Generatorの動作
• 呼び出されると イテレータの一種であるgeneratorオブジェ
クトを返す
• generatorのnext()メソッドを呼び出すと、関数のyield文ま
でを実行してその値を返す
• yield文に遭遇せずに関数が終了すると、StopIteration例
外を送出する
def gen():
yield 'a'
yield 'b'
同じ
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
Copyright c 2004 Python Japan User's Group.
Lightweight Language
Weekend ls-lRシェル
Generatorのメリット
• 簡単にイテレータを作成する事ができる
• 処理状態をどこかに退避する必要がない
• 速い!
– クラスを使ったイテレータより100%以上速いケー
スも
– ほとんどローカル変数のみで実行できる
– 状態を退避する必要がない
Copyright c 2004 Python Japan User's Group.
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]
• イテレータクラスより簡単
• 高速!
Copyright c 2004 Python Japan User's Group.
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 ...
Copyright c 2004 Python Japan User's Group.
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,
Copyright c 2004 Python Japan User's Group.
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
結果
test1()
0.72秒
test2()
0.59秒
test3()
1.08秒
•イテレータの二段重ねでもそれほど遅くはな
らない
•柔軟性とのトレードオフ
Copyright c 2004 Python Japan User's Group.
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
Copyright c 2004 Python Japan User's Group.
Lightweight Language
Weekend ls-lRシェル
終わりに
• イテレータを使おう。イテレータは
Pythonicだ。
• Generatorを使おう。Generatorは
魔法じゃない。
Copyright c 2004 Python Japan User's Group.