답변:
다른 옵션은이 itertools.tee()
함수를 사용하여 두 번째 버전의 생성기를 만드는 것입니다.
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 의 단점은 스토리지입니다. 전체 값 목록이 메모리에 저장됩니다. 값이 너무 많으면 실용적이지 않을 수 있습니다.
그래서 당신은 고전적인 메모리 대 프로세싱 트레이드 오프를 가지고 있습니다. 값을 저장하거나 다시 계산하지 않고 생성기를 되 감는 방법을 상상할 수 없습니다.
오래된 문제에 대해 다른 해결책을 제시하고 싶습니다.
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()
목표를 달성 하는 데 사용할 수 있습니다. 향상된 생성기 및 수율 표현에 대한 자세한 내용 은 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()
전체 iterable을 메모리에 넣습니다
tee()
만약 하나의 이터레이터가 모든 값을 소비한다면 그렇게 될 것 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)
반복자를 재설정 할 수있는 옵션이 없습니다. 반복자는 일반적으로 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 ()를 이 방법으로 반복자를 만든 다음 그 값을 통해 반복자 의지 루프를 통해 루프를 실행합니다.
예를 들면 다음과 같습니다.
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__
내 대답은 약간 다른 문제를 해결합니다. 생성기가 초기화하는 데 비용이 많이 들고 생성 된 각 객체가 생성하는 데 비용이 많이 듭니다. 그러나 여러 기능에서 발전기를 여러 번 사용해야합니다. 생성기와 생성 된 각 객체를 정확히 한 번만 호출하기 위해 스레드를 사용하고 각 소비 메소드를 다른 스레드에서 실행할 수 있습니다. GIL로 인해 진정한 병렬 처리를 달성하지 못할 수도 있지만 목표를 달성 할 것입니다.
딥 러닝 모델은 많은 이미지를 처리합니다. 결과적으로 이미지의 많은 객체에 대한 많은 마스크가 만들어집니다. 각 마스크는 메모리를 소비합니다. 통계와 메트릭을 다르게 만드는 약 10 가지 방법이 있지만 모든 이미지를 한 번에 가져옵니다. 모든 이미지가 메모리에 맞지 않을 수 있습니다. 반복자를 수용하기 위해 모에 토트를 쉽게 다시 작성할 수 있습니다.
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
12 34
for i in y: print i
exec(code1)
for i in y: print i
12 34
exec
간단한 경우에는 약간 권장하지 않는 방법이 있습니다.
y = list(y)
나머지 코드를 그대로 사용하십시오.