使用生成器进行向前查看
我在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]