如何展望Python生成器中的一个元素(peek)?

如何展望Python生成器中的一个元素(peek)?,python,generator,peek,Python,Generator,Peek,我不知道如何在Python生成器中提前查看一个元素。我一看它就不见了 我的意思是: 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.

我不知道如何在Python生成器中提前查看一个元素。我一看它就不见了

我的意思是:

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是一种方法:你不能向后推你读过的元素。但您可以使用创建一个新迭代器,并在元素前面加上前缀:

import itertools

gen = iter([1,2,3])
peek = gen.next()
print list(itertools.chain([peek], gen))
这将起作用——它缓冲一个项,并使用序列中的每个项和下一项调用函数

您的需求对于序列末尾发生的事情是模糊的。当你处于最后一个位置时,“展望未来”是什么意思

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

好吧,晚了两年,但我遇到了这个问题,没有找到任何令我满意的答案。提出了这个元生成器:

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
i、 e.在迭代过程中,您可以随时访问列表中的下一项

您应该使用(i-1,i),而不是使用项目(i,i+1),其中“i”是当前项目,i+1是“向前看”版本,其中“i-1”是生成器的先前版本

以这种方式调整算法将产生与当前相同的结果,除了尝试“向前看”的额外不必要的复杂性之外


向前看是错误的,您不应该这样做。

您可以使用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)
“物品”生成器不受您骚扰“偷窥者”的影响。请注意,在调用“tee”后,不应使用原始的“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]
亚伦:

有了这一点,以下工作将起作用:

>>> 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)。

为了完整起见,(可能是任何Python程序员工具箱的一部分)包括一个实现此行为的
可查看的
包装器。如中的代码示例所示:


但是,通常可以重写使用此功能的代码,使其实际上不需要它。例如,问题中真实的代码示例可以这样编写:

gen = element_generator()
command = gen.next_value()
if command == 'STOP':
  quit_application()
else:
  process(command)
for elem in gen:
    ...
    peek = next(gen)
    gen = itertools.chain([peek], gen)
def peek(it):
    first = next(it)
    return first, itertools.chain([first], it)

(读者注意:我在写这篇文章时保留了示例中的语法,尽管它指的是过时的Python版本)

尽管
itertools.chain()
是这项工作的自然工具,但要注意这样的循环:

gen = element_generator()
command = gen.next_value()
if command == 'STOP':
  quit_application()
else:
  process(command)
for elem in gen:
    ...
    peek = next(gen)
    gen = itertools.chain([peek], gen)
def peek(it):
    first = next(it)
    return first, itertools.chain([first], it)

…因为这将消耗线性增长的内存量,并最终导致停止。(这段代码实际上似乎创建了一个链表,每个chain()调用一个节点。)我之所以知道这一点,不是因为我检查了libs,而是因为这导致了我的程序的严重减速——去掉了
gen=itertools.chain([peek],gen)
行再次加速了它。(Python 3.3)

一个简单的解决方案是使用如下函数:

gen = element_generator()
command = gen.next_value()
if command == 'STOP':
  quit_application()
else:
  process(command)
for elem in gen:
    ...
    peek = next(gen)
    gen = itertools.chain([peek], gen)
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
答案的Python3代码段:


创建一个类在
\uuuu iter\uuuu
上执行此操作非常简单,它只生成
prev
项,并将
elm
放在某个属性中。

如果有人感兴趣,请纠正我的错误,但我相信向任何迭代器添加一些推送功能非常容易

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)

w、 在r.t@davidz的帖子中,更新的工具可以将包装的迭代器重置为先前的位置

>>> 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
有一个功能

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

一个迭代器,可以查看下一个元素,也可以查看前面的元素。它根据需要提前读取并记住
deque
中的值

from collections import deque

class PeekIterator:

    def __init__(self, iterable):
        self.iterator = iter(iterable)
        self.peeked = deque()

    def __iter__(self):
        return self

    def __next__(self):
        if self.peeked:
            return self.peeked.popleft()
        return next(self.iterator)

    def peek(self, ahead=0):
        while len(self.peeked) <= ahead:
            self.peeked.append(next(self.iterator))
        return self.peeked[ahead]
从集合导入数据
类迭代器:
定义初始值(自,可编辑):
self.iterator=iter(iterable)
self.peek=deque()
定义(自我):
回归自我
定义下一个(自我):
如果自己偷看:
return self.peek.popleft()
返回下一个(self.iterator)
def peek(自身,前方=0):
而len(self.peek)>>it=peek迭代器(范围(10))
>>>it.peek()
0
>>>it.peek(5)
5.
>>>it.peek(13)
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
it.peek(13)
文件“[…]”,第15行,在peek中
self.peek.append(下一步(self.iterator))
停止迭代
>>>it.peek(2)
2.
>>>下一个(it)
0
>>>it.peek(2)
3.
>>>列表(it)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

对于那些崇尚节俭和一行程序的人,我向你们展示一个一行程序,它允许我们在iterable中展望未来(这只适用于Python 3.8及更高版本):

>>根据需要导入itertools
>>>peek=lambda iterable,n=1:it.islice(zip(it.chain((t:=it.tee(iterable))[0],[None]*n),it.chain([None]*n,t[1]),n,None)
>>>对于前瞻,peek(范围(10))中的元素:
...     打印(前瞻,元素)
1 0
2 1
3 2
4 3
5 4
6 5
7 6
8 7
9 8
无9
>>>对于前瞻性,元素在peek中(范围(10),2):
...     打印(前瞻,元素)
2 0
3 1
4 2
5 3
6 4
7 5
8 6
9 7
无8
无9
通过避免多次复制迭代器,此方法节省了空间。由于它是如何懒洋洋地生成元素的,所以速度也很快。最后,作为顶部的樱桃,你可以展望套利