LoginSignup
37
40

More than 3 years have passed since last update.

Python のジェネレータを何回もイテレートしたい

Last updated at Posted at 2017-03-09

Pythonのジェネレータは便利ですが、for文で一度ループに使用したあと、またfor文で使おうとするとカラッポのようになります。

def i(n):
    yield n + 1
    yield n + 2


g = i(10)
print('first time:')
for n in g:
    print(n)

print('second time:')
for n in g:
    print(n)

出力は

first time:
11
12
second time:

となります。2回目のforループでは要素がひとつもイテレートされていません。

2回目以降のforループでも1112が帰ってきてほしい、というようなシチュエーションの場合、以下のようなテクニックを使うとうまくいきました。

class ReiteratableWrapper(object):
    def __init__(self, f):
        self._f = f

    def __iter__(self):
        return self._f()

def i(n):
    yield n + 1
    yield n + 2

import functools
f = functools.partial(i, 10)

g2 = ReiteratableWrapper(f)
for n in g2:
    print(n)

for n in g2:
    print(n)

解説

ReiteratableWrapper クラスはジェネレータ関数をひとつ受け取ります。内部では引数を与えずに呼び出すので、引数をともなったジェネレータ呼び出しを行いたい場合は例のように functools.partial 関数などを使って引数をバインドした引数なしの関数を作成しておきます。

for文のinのあとのコンテキストで評価された式は __iter__ メソッドが呼ばれます。
そのため、ReiteratableWrapperのインスタンスは毎回あらたにジェネレータを自動的に作ってくれるようになります。

ただし__iter__メソッドが評価されるたびにジェネレータ関数を評価し、実行していますのでジェネレータ関数の実行回数に制限があるなどジェネレータ関数呼び出しに副作用がある場合は何回も繰り返し使える、とはならないことがあるかと思います。

ファイルから順番に読み込んでメモリを節約する、DBに毎回クエリを発行して順番にイテレーションする、など繰り返し行える内容には今回紹介したテクニックは有効と思います。

参考リンク

37
40
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
37
40