使用前瞻与发电机

jen*_*ena 18 python generator lookahead

我在Python中实现了一个基于生成器的扫描程序,它将字符串标记为表单的元组(标记类型,标记值):

for token in scan("a(b)"):
    print token
Run Code Online (Sandbox Code Playgroud)

会打印

("literal", "a")
("l_paren", "(")
...
Run Code Online (Sandbox Code Playgroud)

下一个任务意味着解析令牌流,为此,我需要能够从当前的一个项目前面查看一个项目,而不必将指针向前移动.事实上,迭代器和生成器不能一次提供完整的项目序列,但是每个项目根据需要使得前瞻与列表相比有点棘手,因为除非__next__()被调用,否则下一个项目是未知的.

什么可以直接实现基于生成器的前瞻看起来像?目前我正在使用一种解决方法,这意味着从生成器中生成一个列表:

token_list = [token for token in scan(string)]
Run Code Online (Sandbox Code Playgroud)

然后很容易通过以下方式实现前瞻:

try:
    next_token = token_list[index + 1]
except: IndexError:
    next_token = None
Run Code Online (Sandbox Code Playgroud)

当然这只是工作正常.但是考虑到这一点,我的第二个问题就出现了:首先是否真的需要制造scan()发电机?

Ale*_*lli 22

那里有相当不错的答案,但我最喜欢的方法是使用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)
Run Code Online (Sandbox Code Playgroud)

您可以使用此类包装任何迭代器,然后使用.lookahead包装器的属性来了解将来要返回的下一个项目.我想把所有真正的逻辑留给itertools.tee并提供这种薄胶! - )


Ned*_*der 14

您可以编写一个包装器来缓冲生成器中的一些项目,并提供一个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]
Run Code Online (Sandbox Code Playgroud)

  • 你可以使用deque作为缓冲区,虽然效率可能无关紧要*对于小型前瞻也是如此. (3认同)

Pau*_*McG 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)
Run Code Online (Sandbox Code Playgroud)

打印:

this:A next:B
this:B next:C
this:C next:D
this:D next:E
this:E next:F
this:F next:None
Run Code Online (Sandbox Code Playgroud)