LoginSignup
17
15

More than 5 years have passed since last update.

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

Posted at

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

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

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

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

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

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

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

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

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

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
Python
print_fib_twice3(fibonacci_generator())
結果
TypeError: Must supply a container
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!

17
15
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15