파이썬 생성기에서 한 요소를 미리 보는 방법을 알 수 없습니다. 내가 보자 마자 사라졌습니다.
내가 의미하는 바는 다음과 같습니다.
gen = iter([1,2,3])
next_value = gen.next() # okay, I looked forward and see that next_value = 1
# but now:
list(gen) # is [2, 3] -- the first value is gone!
다음은 더 실제적인 예입니다.
gen = element_generator()
if gen.next_value() == 'STOP':
quit_application()
else:
process(gen.next())
하나의 요소를 앞으로 볼 수있는 생성기를 작성하도록 도와 줄 사람이 있습니까?
답변
Python 생성기 API는 한 가지 방법입니다. 읽은 요소를 푸시 백 할 수 없습니다. 그러나 itertools 모듈을 사용하여 새 반복자를 만들고 요소 앞에 추가 할 수 있습니다 .
import itertools
gen = iter([1,2,3])
peek = gen.next()
print list(itertools.chain([peek], gen))
답변
완전성을 위해 more-itertools
패키지 (아마도 Python 프로그래머 도구 상자의 일부 여야 함)에는 peekable
이 동작을 구현 하는 래퍼가 포함되어 있습니다. 문서 의 코드 예제는 다음을 보여줍니다.
>>> p = peekable(['a', 'b'])
>>> p.peek()
'a'
>>> next(p)
'a'
그러나 실제로 필요하지 않도록이 기능을 사용하는 코드를 다시 작성할 수 있습니다. 예를 들어, 질문의 실제 코드 샘플은 다음과 같이 작성할 수 있습니다.
gen = element_generator()
command = gen.next_value()
if command == 'STOP':
quit_application()
else:
process(command)
(독자 주 : 구버전의 Python을 참조하더라도이 글을 작성할 때의 질문에서 예제의 구문을 보존했습니다.)
답변
좋아-2 년이 늦었지만이 질문을 보았지만 만족스러운 답을 찾지 못했습니다. 이 메타 생성기로 나타났습니다.
class Peekorator(object):
def __init__(self, generator):
self.empty = False
self.peek = None
self.generator = generator
try:
self.peek = self.generator.next()
except StopIteration:
self.empty = True
def __iter__(self):
return self
def next(self):
"""
Return the self.peek element, or raise StopIteration
if empty
"""
if self.empty:
raise StopIteration()
to_return = self.peek
try:
self.peek = self.generator.next()
except StopIteration:
self.peek = None
self.empty = True
return to_return
def simple_iterator():
for x in range(10):
yield x*3
pkr = Peekorator(simple_iterator())
for i in pkr:
print i, pkr.peek, pkr.empty
결과 :
0 3 False
3 6 False
6 9 False
9 12 False
...
24 27 False
27 None False
즉, 반복하는 동안 언제든지 목록의 다음 항목에 액세스 할 수 있습니다.
답변
itertools.tee를 사용하여 생성기의 경량 사본을 생성 할 수 있습니다. 그런 다음 한 사본을 미리 들여다 보면 두 번째 사본에는 영향을 미치지 않습니다.
import itertools
def process(seq):
peeker, items = itertools.tee(seq)
# initial peek ahead
# so that peeker is one ahead of items
if next(peeker) == 'STOP':
return
for item in items:
# peek ahead
if next(peeker) == "STOP":
return
# process items
print(item)
‘항목’생성기는 ‘피커’를 성추행해도 영향을받지 않습니다. ‘티’를 호출 한 후 원래 ‘seq’를 사용하면 안됩니다.
FWIW, 이것은 이 문제를 해결 하는 잘못된 방법입니다. 생성기에서 1 개 항목을 미리 봐야하는 알고리즘은 현재 생성기 항목과 이전 항목을 사용하도록 작성 될 수 있습니다. 그러면 생성기 사용을 망칠 필요가 없으며 코드가 훨씬 간단 해집니다. 이 질문에 대한 다른 답변을 참조하십시오.
답변
>>> gen = iter(range(10))
>>> peek = next(gen)
>>> peek
0
>>> gen = (value for g in ([peek], gen) for value in g)
>>> list(gen)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
답변
재미를 위해 Aaron의 제안에 따라 lookahead 클래스 구현을 만들었습니다.
import itertools
class lookahead_chain(object):
def __init__(self, it):
self._it = iter(it)
def __iter__(self):
return self
def next(self):
return next(self._it)
def peek(self, default=None, _chain=itertools.chain):
it = self._it
try:
v = self._it.next()
self._it = _chain((v,), it)
return v
except StopIteration:
return default
lookahead = lookahead_chain
이를 통해 다음이 작동합니다.
>>> t = lookahead(xrange(8))
>>> list(itertools.islice(t, 3))
[0, 1, 2]
>>> t.peek()
3
>>> list(itertools.islice(t, 3))
[3, 4, 5]
이 구현에서는 peek를 연속으로 여러 번 호출하는 것이 좋지 않습니다.
CPython 소스 코드를 살펴보면서 더 짧고 효율적인 방법을 찾았습니다.
class lookahead_tee(object):
def __init__(self, it):
self._it, = itertools.tee(it, 1)
def __iter__(self):
return self._it
def peek(self, default=None):
try:
return self._it.__copy__().next()
except StopIteration:
return default
lookahead = lookahead_tee
사용법은 위와 동일하지만 연속해서 여러 번 peek를 사용하기 위해 여기에서 가격을 지불하지 않습니다. 몇 줄을 더 사용하면 반복기에서 둘 이상의 항목을 미리 볼 수도 있습니다 (사용 가능한 RAM까지).
답변
항목 (i, i + 1)을 사용하는 대신, 여기서 ‘i’는 현재 항목이고 i + 1은 ‘앞서보기’버전 인 경우 (i-1, i)를 사용해야합니다. 여기서 ‘i-1’ 생성기의 이전 버전입니다.
이런 방식으로 알고리즘을 조정하면 ‘앞서 살펴보기’를 시도하는 불필요한 복잡성을 제외하고 현재 보유하고있는 것과 동일한 것을 생성 할 수 있습니다.
앞을 들여다 보는 것은 실수이며 그렇게해서는 안됩니다.