programing

Python 생성기에서 한 요소 (peek)를 미리 보는 방법은 무엇입니까?

nasanasas 2020. 11. 16. 21:37
반응형

Python 생성기에서 한 요소 (peek)를 미리 보는 방법은 무엇입니까?


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

내가 의미하는 바는 다음과 같습니다.

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(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

사용법은 위와 동일하지만 연속으로 여러 번 peek를 사용하기 위해 여기에서 가격을 지불하지 않습니다. 몇 줄을 더 사용하면 반복기에서 둘 이상의 항목을 미리 볼 수도 있습니다 (사용 가능한 RAM까지).


항목 (i, i + 1)을 사용하는 대신, 여기서 'i'는 현재 항목이고 i + 1은 '앞서보기'버전 인 경우 (i-1, i)를 사용해야합니다. 여기서 'i-1' 생성기의 이전 버전입니다.

이런 방식으로 알고리즘을 조정하면 '앞서 살펴보기'를 시도하는 불필요한 복잡성을 제외하고 현재 보유하고있는 것과 동일한 것을 생성 할 수 있습니다.

앞을 들여다 보는 것은 실수이며 그렇게해서는 안됩니다.


이것은 작동합니다-항목을 버퍼링하고 시퀀스의 각 항목과 다음 항목으로 함수를 호출합니다.

시퀀스가 끝날 때 발생하는 일에 대한 요구 사항이 모호합니다. 마지막에있을 때 "미리보기"는 무엇을 의미합니까?

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)

... 이는 선형 적으로 증가하는 메모리 양을 소비하고 결국 중단되기 때문입니다. (이 코드는 본질적으로 chain () 호출 당 하나의 노드로 연결된 목록을 생성하는 것처럼 보입니다.) libs를 검사했기 때문이 아니라 이로 인해 프로그램이 크게 느려지기 때문에이 gen = itertools.chain([peek], gen)을 제거하면 속도가 빨라졌습니다. 다시. (Python 3.3)


Python3 snippet for @jonathan-hartley answer:

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)

It'd be straightforward to create a class that does this on __iter__ and yields just the prev item and put the elm in some attribute.


w.r.t @David Z's post, the newer seekable tool can reset a wrapped iterator to a prior position.

>>> 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 has a peek function.

>> from cytoolz import peek
>> gen = iter([1,2,3])
>> first, continuation = peek(gen)
>> first
1
>> list(continuation)
[1, 2, 3]

참고URL : https://stackoverflow.com/questions/2425270/how-to-look-ahead-one-element-peek-in-a-python-generator

반응형