回答:
別のオプションは、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の欠点はストレージです。値のリスト全体がメモリに保存されます。値が多すぎると、実用的でない場合があります。
つまり、従来のメモリと処理のトレードオフがあります。値を保存したり、計算し直したりせずにジェネレータを巻き戻す方法は想像できません。
古い問題に別の解決策を提供したい
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()
。これは、上記のトレードオフそのメモリ対処理の一部を含むが、それは可能性があるだけで発電した結果を格納比べていくつかのメモリを節約します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)
、あなたのケースでは代わりに使用するのが最善です。
list()
tee()
、1つのイテレータがすべての値を消費する場合-それが機能する方法tee
です。
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
か?
イテレータをリセットするオプションはありません。イテレータは通常、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>)
これで、使用することができます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)
注:イテレータを進めるとメモリの消費量が増えるので、大きなイテラブルに注意してください。
itertools.cycle() を使用してそれを行うには、このメソッドでイテレーターを作成し、そのイテレーターでforループを実行して、その値をループします。
例えば:
def generator():
for j in cycle([i for i in range(5)]):
yield j
gen = generator()
for i in range(20):
print(next(gen))
0から4までの20個の数値が繰り返し生成されます。
ドキュメントからのメモ:
Note, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).
わかりました、ジェネレーターを複数回呼び出したいと言いますが、初期化は高価です...このようなものはどうですか?
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-types http://anandology.com/python-practice-book/iterators.html
__call__
私の答えは少し異なる問題を解決します:ジェネレーターの初期化にコストがかかり、生成された各オブジェクトの生成にコストがかかる場合。しかし、複数の関数でジェネレータを複数回使用する必要があります。ジェネレーターと生成された各オブジェクトを1回だけ呼び出すために、スレッドを使用し、さまざまなスレッドで各消費メソッドを実行できます。GILにより、真の並列処理を実現できない可能性がありますが、目標は達成できます。
このアプローチは、ディープラーニングモデルが多くの画像を処理する次の場合にうまく機能しました。その結果、画像上の多くのオブジェクトに対して多くのマスクが作成されます。各マスクはメモリを消費します。さまざまな統計やメトリックを作成する約10のメソッドがありますが、それらはすべての画像を一度に取得します。すべての画像がメモリに収まりません。moethodsは、イテレータを受け入れるように簡単に書き換えることができます。
class GeneratorSplitter:
'''
Split a generator object into multiple generators which will be sincronised. Each call to each of the sub generators will cause only one call in the input generator. This way multiple methods on threads can iterate the input generator , and the generator will cycled only once.
'''
def __init__(self, gen):
self.gen = gen
self.consumers: List[GeneratorSplitter.InnerGen] = []
self.thread: threading.Thread = None
self.value = None
self.finished = False
self.exception = None
def GetConsumer(self):
# Returns a generator object.
cons = self.InnerGen(self)
self.consumers.append(cons)
return cons
def _Work(self):
try:
for d in self.gen:
for cons in self.consumers:
cons.consumed.wait()
cons.consumed.clear()
self.value = d
for cons in self.consumers:
cons.readyToRead.set()
for cons in self.consumers:
cons.consumed.wait()
self.finished = True
for cons in self.consumers:
cons.readyToRead.set()
except Exception as ex:
self.exception = ex
for cons in self.consumers:
cons.readyToRead.set()
def Start(self):
self.thread = threading.Thread(target=self._Work)
self.thread.start()
class InnerGen:
def __init__(self, parent: "GeneratorSplitter"):
self.parent: "GeneratorSplitter" = parent
self.readyToRead: threading.Event = threading.Event()
self.consumed: threading.Event = threading.Event()
self.consumed.set()
def __iter__(self):
return self
def __next__(self):
self.readyToRead.wait()
self.readyToRead.clear()
if self.parent.finished:
raise StopIteration()
if self.parent.exception:
raise self.parent.exception
val = self.parent.value
self.consumed.set()
return val
使用法:
genSplitter = GeneratorSplitter(expensiveGenerator)
metrics={}
executor = ThreadPoolExecutor(max_workers=3)
f1 = executor.submit(mean,genSplitter.GetConsumer())
f2 = executor.submit(max,genSplitter.GetConsumer())
f3 = executor.submit(someFancyMetric,genSplitter.GetConsumer())
genSplitter.Start()
metrics.update(f1.result())
metrics.update(f2.result())
metrics.update(f3.result())
itertools.islice
asyncのために再発明するかaiostream.stream.take
、そしてこの投稿はasyn / awaitの方法でそれを可能にしますstackoverflow.com/a/42379188/149818
これは、コードオブジェクトによって実行できます。これがその例です。
code_str="y=(a for a in [1,2,3,4])"
code1=compile(code_str,'<string>','single')
exec(code1)
for i in y: print i
1 2 3 4
for i in y: print i
exec(code1)
for i in y: print i
1 2 3 4
exec
そのような単純なケースではわずかに推奨されないことを含みます。
y = list(y)
、残りのコードを変更せずに使用することもできます。