下記の記事の最後にジェネレータはコンテナ化した方がいいと書きました。
今回はこれを説明します。
まずは前回同様フィボナッチ数を生成するジェネレータを作成します。
前回の記事のコメントで洗練された書き方を教えてもらったのでそっちを使います。
# ジェネレータ
def fibonacci_generator():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# コンテナ化されたジェネレータ
class FibonacciGenerator(object):
def __iter__(self):
a, b = 0, 1
while True:
yield a
a, b = b, a + b
これらは同じ結果を出力します。
for fib in fibonacci_generator():
if fib > 1000:
break
print fib,
print
for fib in FibonacciGenerator():
if fib > 1000:
break
print fib,
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
上記の 2つの for 文はジェネレータだけが違うことに注意してください。
ジェネレータが変わるごとにこのコードを書くのは面倒なので、関数化してみましょう。
def print_fib(fib_iterator, limit = 1000):
for fib in fib_iterator:
if fib > limit:
break
print fib,
print
print_fib(fibonacci_generator())
print_fib(FibonacciGenerator())
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
ジェネレータ受け取る関数を作ることですっきりしました。
では、ジェネレータの内容を2回表示するようにこの関数を変更してみましょう。
def print_fib_twice(fib_iterator, limit = 100):
print "[1]",
for fib in fib_iterator:
if fib > limit:
break
print fib,
print " [2]",
for fib in fib_iterator:
if fib > limit:
break
print fib,
print
print_fib_twice(fibonacci_generator())
print_fib_twice(FibonacciGenerator())
[1] 0 1 1 2 3 5 8 13 21 34 55 89 [2]
[1] 0 1 1 2 3 5 8 13 21 34 55 89 [2] 0 1 1 2 3 5 8 13 21 34 55 89
なんと、コンテナ化されていないジェネレータの方は、2回目の内容が表示されません。
この原因は、ジェネレータをそのまま使うと1回目と2回目で同じものが使い回されるためです。
コンテナ化されたジェネレータでは、ループごとに新しいジェネレータを作成してくれます。
これは次のようにしてみると分かりやすいです。
def print_fib_twice2(fib_iterator, limit1, limit2):
print "[1]",
for fib in fib_iterator:
if fib > limit1:
break
print fib,
print " [2]",
for fib in fib_iterator:
if fib > limit2:
break
print fib,
print
print_fib_twice2(fibonacci_generator(), limit1 = 100, limit2 = 400)
print_fib_twice2(FibonacciGenerator(), limit1 = 100, limit2 = 400)
[1] 0 1 1 2 3 5 8 13 21 34 55 89 [2] 233 377
[1] 0 1 1 2 3 5 8 13 21 34 55 89 [2] 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377
このように、ジェネレータは内部で複数回使われるような関数に渡されると、思わぬエラーの元になります。
なので、一般的にはコンテナ化しておいた方が安全です。
逆に、イテレータを受け取るような関数を書く場合、コンテナ化されているかどうかのチェックを入れた方が良いです。
イテレータがコンテナ化されているかどうかは、iter()
を2回適用してみて、異なるイテレータが返ってくるかをチェックすることで判定できます。
def print_fib_twice3(fib_iterator, limit = 100):
if iter(fib_iterator) is iter(fib_iterator):
raise TypeError("Must supply a container")
print "[1]",
for fib in fib_iterator:
if fib > limit:
break
print fib,
print " [2]",
for fib in fib_iterator:
if fib > limit:
break
print fib,
print
print_fib_twice3(fibonacci_generator())
TypeError: Must supply a container
print_fib_twice3(FibonacciGenerator())
[1] 0 1 1 2 3 5 8 13 21 34 55 89 [2] 0 1 1 2 3 5 8 13 21 34 55 89
これで意図しない動作を防ぐことができます。
Enjoy!