使用生成器进行向前查看

20 投票
9 回答
4160 浏览
提问于 2025-04-15 14:48

我在Python中实现了一个基于生成器的扫描器,它可以把一个字符串分解成一系列的元组,格式是 (标记类型, 标记值)

for token in scan("a(b)"):
    print token

这段代码会输出:

("literal", "a")
("l_paren", "(")
...

接下来的任务是解析这些标记流。为此,我需要能够查看当前标记之后的一个标记,但又不想把指针往前移动。由于迭代器和生成器不会一次性提供所有的项目,而是根据需要一个个提供,所以想要提前查看下一个项目就变得有点复杂。因为只有在调用 __next__() 的时候,才能知道下一个项目是什么。

那么,一个简单的基于生成器的提前查看的实现应该是什么样的呢?目前我使用的解决方法是把生成器转换成一个列表:

token_list = [token for token in scan(string)]

这样就可以很容易地实现提前查看,像这样:

try:
    next_token = token_list[index + 1]
except: IndexError:
    next_token = None

当然,这样做是没问题的。但是仔细想想,我就有了第二个问题:一开始让 scan() 成为一个生成器真的有必要吗?

9 个回答

6

这段代码看起来不太美观,但可能能满足你的需求:

def paired_iter(it):
    token = it.next()
    for lookahead in it:
        yield (token, lookahead)
        token = lookahead
    yield (token, None)

def scan(s):
    for c in s:
        yield c

for this_token, next_token in paired_iter(scan("ABCDEF")):
    print "this:%s next:%s" % (this_token, next_token)

输出结果是:

this:A next:B
this:B next:C
this:C next:D
this:D next:E
this:E next:F
this:F next:None
23

这里有一些不错的回答,但我最喜欢的方法是使用 itertools.tee。这个工具可以接收一个迭代器,然后返回两个(或者更多,如果需要的话),这些迭代器可以独立地向前推进。它会在内存中只保存必要的部分(也就是说,如果这些迭代器之间的步伐没有差得太远,所需的内存就不会很多)。比如:

import itertools
import collections

class IteratorWithLookahead(collections.Iterator):
  def __init__(self, it):
    self.it, self.nextit = itertools.tee(iter(it))
    self._advance()
  def _advance(self):
    self.lookahead = next(self.nextit, None)
  def __next__(self):
    self._advance()
    return next(self.it)

你可以用这个类来包装任何迭代器,然后使用这个包装器的 .lookahead 属性来知道将来会返回的下一个项目是什么。我喜欢把所有的实际逻辑都交给 itertools.tee,只提供一个简单的连接!-)

16

你可以写一个包装器,这个包装器可以从生成器中缓存一些项目,并提供一个lookahead()函数来查看这些缓存的项目:

class Lookahead:
    def __init__(self, iter):
        self.iter = iter
        self.buffer = []

    def __iter__(self):
        return self

    def next(self):
        if self.buffer:
            return self.buffer.pop(0)
        else:
            return self.iter.next()

    def lookahead(self, n):
        """Return an item n entries ahead in the iteration."""
        while n >= len(self.buffer):
            try:
                self.buffer.append(self.iter.next())
            except StopIteration:
                return None
        return self.buffer[n]

撰写回答