Pythonのジェネレータ作成時の注意点

下記の記事の最後にジェネレータはコンテナ化した方がいいと書きました。

- [Pythonのクロージャ、イテレータ、ジェネレータでフィボナッチ数を生成す](http://qiita.com/hoxo_m/items/c61934a0e7be35d683ee)

今回はこれを説明します。

まずは前回同様フィボナッチ数を生成するジェネレータを作成します。
前回の記事のコメントで洗練された書き方を教えてもらったのでそっちを使います。

```py:Python
# ジェネレータ
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
```

これらは同じ結果を出力します。

```py:Python
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 文はジェネレータだけが違うことに注意してください。
ジェネレータが変わるごとにこのコードを書くのは面倒なので、関数化してみましょう。

```py:Python
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回表示するようにこの関数を変更してみましょう。

```py:Python
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回目で同じものが使い回されるためです。
コンテナ化されたジェネレータでは、ループごとに新しいジェネレータを作成してくれます。
これは次のようにしてみると分かりやすいです。

```py:Python
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回適用してみて、異なるイテレータが返ってくるかをチェックすることで判定できます。

```py:Python
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
```

```py:Python
print_fib_twice3(fibonacci_generator())
```

```:結果
TypeError: Must supply a container
```

```py:Python
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!