web-dev-qa-db-ja.com

Pythonでジェネレーターオブジェクトをリセットする

複数のyieldによって返されるジェネレーターオブジェクトがあります。このジェネレーターを呼び出す準備は、かなり時間がかかります。それが私がジェネレータを何度も再利用したい理由です。

y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)

もちろん、コンテンツを単純なリストにコピーすることを心がけています。

131
Dewfy

別のオプションは、 itertools.tee() 関数を使用して、ジェネレーターの2番目のバージョンを作成することです。

y = FunctionWithYield()
y, y_backup = tee(y)
for x in y:
    print(x)
for x in y_backup:
    print(x)

元の反復ですべてのアイテムを処理できない場合、これはメモリ使用量の観点から有益です。

105
Ants Aasma

発電機は巻き戻すことができません。次のオプションがあります。

  1. ジェネレーター関数を再度実行して、生成を再開します。

    y = FunctionWithYield()
    for x in y: print(x)
    y = FunctionWithYield()
    for x in y: print(x)
    
  2. ジェネレーターの結果をメモリまたはディスク上のデータ構造に保存し、再度反復することができます。

    y = list(FunctionWithYield())
    for x in y: print(x)
    # can iterate again:
    for x in y: print(x)
    

オプション1の欠点は、値を再計算することです。 CPUに負荷がかかる場合は、2回計算することになります。一方、2の欠点はストレージです。値のリスト全体がメモリに保存されます。値が多すぎる場合は、実用的ではありません。

したがって、古典的なメモリと処理のトレードオフがあります。値を保存するか、再度計算せずにジェネレーターを巻き戻す方法は想像できません。

131
nosklo
>>> def gen():
...     def init():
...         return 0
...     i = init()
...     while True:
...         val = (yield i)
...         if val=='restart':
...             i = init()
...         else:
...             i += 1

>>> g = gen()
>>> g.next()
0
>>> g.next()
1
>>> g.next()
2
>>> g.next()
3
>>> g.send('restart')
0
>>> g.next()
1
>>> g.next()
2
30
aaab

おそらく最も簡単な解決策は、高価な部分をオブジェクトにラップし、それをジェネレーターに渡すことです。

_data = ExpensiveSetup()
for x in FunctionWithYield(data): pass
for x in FunctionWithYield(data): pass
_

これにより、高価な計算をキャッシュできます。

すべての結果をRAMに同時に保持できる場合は、list()を使用して、ジェネレーターの結果をプレーンリストに具体化し、それを操作します。

28
Aaron Digulla

古い問題に対する別の解決策を提供したい

_class IterableAdapter:
    def __init__(self, iterator_factory):
        self.iterator_factory = iterator_factory

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

squares = IterableAdapter(lambda: (x * x for x in range(5)))

for x in squares: print(x)
for x in squares: print(x)
_

list(iterator)のようなものと比較した場合のこの利点は、これがO(1)スペースの複雑さであり、list(iterator)O(n)であることです。欠点は、イテレータにアクセスするだけで、イテレータを生成した関数にはアクセスできない場合、このメソッドを使用できないことです。たとえば、以下を実行するのが妥当と思われるかもしれませんが、動作しません。

_g = (x * x for x in range(5))

squares = IterableAdapter(lambda: g)

for x in squares: print(x)
for x in squares: print(x)
_
16
michaelsnowden

GrzegorzOledzkiの答えでは不十分な場合は、おそらくsend()を使用して目標を達成できます。拡張ジェネレーターとyield式の詳細については、 PEP-0342 を参照してください。

更新: itertools.tee() も参照してください。上記のメモリと処理のトレードオフの一部が関係しますが、mightジェネレータ結果をlistに保存するだけでなくメモリを節約できます;ジェネレータの使用方法に依存します。

5
Hank Gay

出力が渡された引数とステップ番号のみに依存するという意味でジェネレーターが純粋であり、結果のジェネレーターを再起動できるようにしたい場合、便利なソートスニペットを以下に示します。

import copy

def generator(i):
    yield from range(i)

g = generator(10)
print(list(g))
print(list(g))

class GeneratorRestartHandler(object):
    def __init__(self, gen_func, argv, kwargv):
        self.gen_func = gen_func
        self.argv = copy.copy(argv)
        self.kwargv = copy.copy(kwargv)
        self.local_copy = iter(self)

    def __iter__(self):
        return self.gen_func(*self.argv, **self.kwargv)

    def __next__(self):
        return next(self.local_copy)

def restartable(g_func: callable) -> callable:
    def tmp(*argv, **kwargv):
        return GeneratorRestartHandler(g_func, argv, kwargv)

    return tmp

@restartable
def generator2(i):
    yield from range(i)

g = generator2(10)
print(next(g))
print(list(g))
print(list(g))
print(next(g))

出力:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
0
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1
5
Ben Usman

ティーの公式文書 から:

一般に、あるイテレーターが別のイテレーターが開始する前にデータのほとんどまたはすべてを使用する場合、tee()の代わりにlist()を使用する方が高速です。

したがって、代わりにlist(iterable)を使用するのが最善です。

3

ラッパー関数を使用してStopIterationを処理する

ジェネレータが使い果たされたときを追跡するジェネレータ生成関数に簡単なラッパー関数を書くことができます。反復の終わりに達するとジェネレーターがスローするStopIteration例外を使用してそうします。

import types

def generator_wrapper(function=None, **kwargs):
    assert function is not None, "Please supply a function"
    def inner_func(function=function, **kwargs):
        generator = function(**kwargs)
        assert isinstance(generator, types.GeneratorType), "Invalid function"
        try:
            yield next(generator)
        except StopIteration:
            generator = function(**kwargs)
            yield next(generator)
    return inner_func

上記のように、ラッパー関数がStopIteration例外をキャッチすると、ジェネレーターオブジェクトを再初期化します(関数呼び出しの別のインスタンスを使用)。

次に、ジェネレーター供給関数を次のように定義すると仮定すると、Python関数デコレーター構文を使用して暗黙的にラップすることができます。

@generator_wrapper
def generator_generating_function(**kwargs):
    for item in ["a value", "another value"]
        yield item
2
Aalok

ジェネレーターを返す関数を定義できます

def f():
  def FunctionWithYield(generator_args):
    code here...

  return FunctionWithYield

これで、好きなだけ何度でも実行できます。

for x in f()(generator_args): print(x)
for x in f()(generator_args): print(x)
2
SMeznaric

高価な準備が意味することはわかりませんが、実際に持っていると思います

data = ... # Expensive computation
y = FunctionWithYield(data)
for x in y: print(x)
#here must be something to reset 'y'
# this is expensive - data = ... # Expensive computation
# y = FunctionWithYield(data)
for x in y: print(x)

その場合、dataを再利用しないのはなぜですか?

1
ilya n.

more_itertools.seekable (サードパーティのツール)を使用して、イテレータをリセットできるようになりました。

> pip install more_itertools でインストール

import more_itertools as mit


y = mit.seekable(FunctionWithYield())
for x in y:
    print(x)

y.seek(0)                                              # reset iterator
for x in y:
    print(x)

注:メモリの消費量はイテレータを進めているときに増加するため、大きなイテレート可能オブジェクトには注意してください。

1
pylang

イテレータをリセットするオプションはありません。通常、イテレータは、next()関数を反復処理するときにポップアウトします。唯一の方法は、イテレータオブジェクトで反復する前にバックアップを取ることです。以下を確認してください。

アイテム0〜9のイテレータオブジェクトの作成

_i=iter(range(10))
_

ポップアウトするnext()関数を繰り返す

_print(next(i))
_

イテレータオブジェクトをリストに変換する

_L=list(i)
print(L)
output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
_

そのため、アイテム0はすでにポップアウトされています。また、イテレータをリストに変換すると、すべてのアイテムがポップされます。

_next(L) 

Traceback (most recent call last):
  File "<pyshell#129>", line 1, in <module>
    next(L)
StopIteration
_

そのため、反復を開始する前に、イテレータをバックアップ用のリストに変換する必要があります。リストはiter(<list-object>)でイテレータに変換できます

1
Amalraj Victory

ジェネレーターを複数回呼び出したいと言いますが、初期化は高価です...このようなものはどうですか?

class InitializedFunctionWithYield(object):
    def __init__(self):
        # do expensive initialization
        self.start = 5

    def __call__(self, *args, **kwargs):
        # do cheap iteration
        for i in xrange(5):
            yield self.start + i

y = InitializedFunctionWithYield()

for x in y():
    print x

for x in y():
    print x

あるいは、イテレータプロトコルに従って、ある種の「リセット」関数を定義する独自のクラスを作成することもできます。

class MyIterator(object):
    def __init__(self):
        self.reset()

    def reset(self):
        self.i = 5

    def __iter__(self):
        return self

    def next(self):
        i = self.i
        if i > 0:
            self.i -= 1
            return i
        else:
            raise StopIteration()

my_iterator = MyIterator()

for x in my_iterator:
    print x

print 'resetting...'
my_iterator.reset()

for x in my_iterator:
    print x

https://docs.python.org/2/library/stdtypes.html#iterator-typeshttp://anandology.com/python-practice-book/iterators.html

0
tvt173