파이썬 생성기에서 하나의 요소 (엿보기)를 보는 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/2425270

  •  19-09-2019
  •  | 
  •  

문제

파이썬 생성기에서 하나의 요소를 앞서 보는 방법을 알 수 없습니다. 내가 보 자마자 사라졌습니다.

다음은 다음과 같습니다.

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 Generator API는 한 가지 방법입니다. 읽은 요소를 뒤로 밀 수는 없습니다. 그러나 당신은 그것을 사용하여 새 반복기를 만들 수 있습니다 itertools 모듈 그리고 요소를 전제하십시오.

import itertools

gen = iter([1,2,3])
peek = gen.next()
print list(itertools.chain([peek], gen))

다른 팁

완전성을 위해 more-itertools 패키지 (아마도 Python 프로그래머의 도구 상자의 일부일 것입니다)는 peekable 이 동작을 구현하는 래퍼. 코드 예제로 문서 쇼 :

>>> p = peekable(xrange(2))
>>> p.peek()
0
>>> p.next()
0
>>> p.peek()
1
>>> p.next()
1

문서에 Python 2 구문이 표시 되더라도 패키지는 Python 2 및 3과 호환됩니다.

좋아 - 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

사용량은 위와 동일하지만 여기에서 가격을 지불하지 않아도 엿보기를 여러 번 연속으로 사용하지 않습니다. 몇 줄이 더 있으면 반복자 (사용 가능한 RAM)에서 두 개 이상의 항목을 앞서 나갈 수도 있습니다.

항목 (i, i+1)을 사용하는 대신 'i'는 현재 항목이고 i+1은 'Peek Peary'버전입니다. 생성기의 이전 버전입니다.

이러한 방식으로 알고리즘을 조정하면 '앞으로 나아가는'불필요한 복잡성을 제외하고는 현재 가지고있는 것과 동일한 것을 생성합니다.

미리 엿보기는 실수이며, 그렇게해서는 안됩니다.

이것은 작동합니다. 항목을 완충하고 각 항목과 함께 함수를 호출하고 다음 항목은 순서대로 호출됩니다.

당신의 요구 사항은 시퀀스의 끝에서 일어나는 일에 대해 어둡습니다. 마지막 사람이있을 때 "미리보기"는 무엇을 의미합니까?

def process_with_lookahead( iterable, aFunction ):
    prev= iterable.next()
    for item in iterable:
        aFunction( prev, item )
        prev= item
    aFunction( item, None )

def someLookaheadFunction( item, next_item ):
    print item, next_item

간단한 솔루션은 다음과 같은 기능을 사용하는 것입니다.

def peek(it):
    first = next(it)
    return first, itertools.chain([first], it)

그런 다음 할 수 있습니다 :

>>> it = iter(range(10))
>>> x, it = peek(it)
>>> x
0
>>> next(it)
0
>>> next(it)
1

누구든지 관심이 있고 내가 틀렸다면 저를 수정 해주세요. 그러나 반복자에 푸시 백 기능을 추가하는 것이 매우 쉽다고 생각합니다.

class Back_pushable_iterator:
    """Class whose constructor takes an iterator as its only parameter, and
    returns an iterator that behaves in the same way, with added push back
    functionality.

    The idea is to be able to push back elements that need to be retrieved once
    more with the iterator semantics. This is particularly useful to implement
    LL(k) parsers that need k tokens of lookahead. Lookahead or push back is
    really a matter of perspective. The pushing back strategy allows a clean
    parser implementation based on recursive parser functions.

    The invoker of this class takes care of storing the elements that should be
    pushed back. A consequence of this is that any elements can be "pushed
    back", even elements that have never been retrieved from the iterator.
    The elements that are pushed back are then retrieved through the iterator
    interface in a LIFO-manner (as should logically be expected).

    This class works for any iterator but is especially meaningful for a
    generator iterator, which offers no obvious push back ability.

    In the LL(k) case mentioned above, the tokenizer can be implemented by a
    standard generator function (clean and simple), that is completed by this
    class for the needs of the actual parser.
    """
    def __init__(self, iterator):
        self.iterator = iterator
        self.pushed_back = []

    def __iter__(self):
        return self

    def __next__(self):
        if self.pushed_back:
            return self.pushed_back.pop()
        else:
            return next(self.iterator)

    def push_back(self, element):
        self.pushed_back.append(element)
it = Back_pushable_iterator(x for x in range(10))

x = next(it) # 0
print(x)
it.push_back(x)
x = next(it) # 0
print(x)
x = next(it) # 1
print(x)
x = next(it) # 2
y = next(it) # 3
print(x)
print(y)
it.push_back(y)
it.push_back(x)
x = next(it) # 2
y = next(it) # 3
print(x)
print(y)

for x in it:
    print(x) # 4-9

하지만 itertools.chain() 여기에서 작업을위한 자연 도구입니다. 다음과 같은 루프를 조심하십시오.

for elem in gen:
    ...
    peek = next(gen)
    gen = itertools.chain([peek], gen)

... 이것은 선형으로 성장하는 메모리의 양을 소비하고 결국 정지로 갈아냅니다. (이 코드는 본질적으로 링크 된 목록, 체인 당 하나의 노드 () 호출을 생성하는 것 같습니다.) LIBS를 검사했기 때문에이 사실이 아니라 단지 내 프로그램의 큰 둔화가 발생했기 때문에 gen = itertools.chain([peek], gen) 라인은 다시 그것을 쏟아 부었습니다. (파이썬 3.3)

Python3 스 니펫 @Jonathan-Hartley 대답:

def peek(iterator, eoi=None):
    iterator = iter(iterator)

    try:
        prev = next(iterator)
    except StopIteration:
        return iterator

    for elm in iterator:
        yield prev, elm
        prev = elm

    yield prev, eoi


for curr, nxt in peek(range(10)):
    print((curr, nxt))

# (0, 1)
# (1, 2)
# (2, 3)
# (3, 4)
# (4, 5)
# (5, 6)
# (6, 7)
# (7, 8)
# (8, 9)
# (9, None)

이 작업을 수행하는 수업을 만드는 것은 간단합니다. __iter__ 그리고 단지 prev 항목을 넣고 elm 일부 속성에서.

WRT @David Z의 게시물, 새로운 게시물 seekable 도구는 포장 된 반복기를 이전 위치로 재설정 할 수 있습니다.

>>> s = mit.seekable(range(3))
>>> s.next()
# 0

>>> s.seek(0)                                              # reset iterator
>>> s.next()
# 0

>>> s.next()
# 1

>>> s.seek(1)
>>> s.next()
# 1

>>> next(s)
# 2

Cytoolz a 몰래 엿보다 기능.

>> from cytoolz import peek
>> gen = iter([1,2,3])
>> first, continuation = peek(gen)
>> first
1
>> list(continuation)
[1, 2, 3]
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top