[python] 파이썬에서 생성기 객체 재설정

여러 수율로 반환 된 생성기 객체가 있습니다. 이 생성기를 호출하기위한 준비는 다소 시간이 걸리는 작업입니다. 그래서 발전기를 여러 번 재사용하고 싶습니다.

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

물론 콘텐츠를 간단한 목록으로 복사하는 것을 고려하고 있습니다. 발전기를 재설정하는 방법이 있습니까?



답변

다른 옵션은이 itertools.tee()함수를 사용하여 두 번째 버전의 생성기를 만드는 것입니다.

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

이것은 원래 반복이 모든 항목을 처리하지 않을 수있는 경우 메모리 사용 관점에서 유리할 수 있습니다.


답변

발전기는 되 감을 수 없습니다. 다음과 같은 옵션이 있습니다.

  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 의 단점은 스토리지입니다. 전체 값 목록이 메모리에 저장됩니다. 값이 너무 많으면 실용적이지 않을 수 있습니다.

그래서 당신은 고전적인 메모리 대 프로세싱 트레이드 오프를 가지고 있습니다. 값을 저장하거나 다시 계산하지 않고 생성기를 되 감는 방법을 상상할 수 없습니다.


답변

>>> 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()목표를 달성 하는 데 사용할 수 있습니다. 향상된 생성기 및 수율 표현에 대한 자세한 내용 은 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