複数のyieldによって返されるジェネレーターオブジェクトがあります。このジェネレーターを呼び出す準備は、かなり時間がかかります。それが私がジェネレータを何度も再利用したい理由です。
y = FunctionWithYield()
for x in y: print(x)
#here must be something to reset 'y'
for x in y: print(x)
もちろん、コンテンツを単純なリストにコピーすることを心がけています。
別のオプションは、 itertools.tee()
関数を使用して、ジェネレーターの2番目のバージョンを作成することです。
y = FunctionWithYield()
y, y_backup = tee(y)
for x in y:
print(x)
for x in y_backup:
print(x)
元の反復ですべてのアイテムを処理できない場合、これはメモリ使用量の観点から有益です。
発電機は巻き戻すことができません。次のオプションがあります。
ジェネレーター関数を再度実行して、生成を再開します。
y = FunctionWithYield()
for x in y: print(x)
y = FunctionWithYield()
for x in y: print(x)
ジェネレーターの結果をメモリまたはディスク上のデータ構造に保存し、再度反復することができます。
y = list(FunctionWithYield())
for x in y: print(x)
# can iterate again:
for x in y: print(x)
オプション1の欠点は、値を再計算することです。 CPUに負荷がかかる場合は、2回計算することになります。一方、2の欠点はストレージです。値のリスト全体がメモリに保存されます。値が多すぎる場合は、実用的ではありません。
したがって、古典的なメモリと処理のトレードオフがあります。値を保存するか、再度計算せずにジェネレーターを巻き戻す方法は想像できません。
>>> 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
おそらく最も簡単な解決策は、高価な部分をオブジェクトにラップし、それをジェネレーターに渡すことです。
_data = ExpensiveSetup()
for x in FunctionWithYield(data): pass
for x in FunctionWithYield(data): pass
_
これにより、高価な計算をキャッシュできます。
すべての結果をRAMに同時に保持できる場合は、list()
を使用して、ジェネレーターの結果をプレーンリストに具体化し、それを操作します。
古い問題に対する別の解決策を提供したい
_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)
_
GrzegorzOledzkiの答えでは不十分な場合は、おそらくsend()
を使用して目標を達成できます。拡張ジェネレーターとyield式の詳細については、 PEP-0342 を参照してください。
更新: itertools.tee()
も参照してください。上記のメモリと処理のトレードオフの一部が関係しますが、mightジェネレータ結果をlist
に保存するだけでなくメモリを節約できます;ジェネレータの使用方法に依存します。
出力が渡された引数とステップ番号のみに依存するという意味でジェネレーターが純粋であり、結果のジェネレーターを再起動できるようにしたい場合、便利なソートスニペットを以下に示します。
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
ティーの公式文書 から:
一般に、あるイテレーターが別のイテレーターが開始する前にデータのほとんどまたはすべてを使用する場合、tee()の代わりにlist()を使用する方が高速です。
したがって、代わりにlist(iterable)
を使用するのが最善です。
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
ジェネレーターを返す関数を定義できます
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)
高価な準備が意味することはわかりませんが、実際に持っていると思います
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
を再利用しないのはなぜですか?
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)
注:メモリの消費量はイテレータを進めているときに増加するため、大きなイテレート可能オブジェクトには注意してください。
イテレータをリセットするオプションはありません。通常、イテレータは、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>)
でイテレータに変換できます
ジェネレーターを複数回呼び出したいと言いますが、初期化は高価です...このようなものはどうですか?
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