2009-10-05 75 views
16

我已经实现在Python发电的扫描仪​​,标记化的字符串到窗体的元组(令牌类型,令牌值)前瞻使用与发电机

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

将打印

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

下一个任务意味着解析令牌流,为此,我需要能够从当前的一个项目前面查看一个项目,而无需向前移动指针。迭代器和生成器不一次提供完整的项目序列,但每个项目根据需要使预览比列表更复杂一些,因为除非调用__next__(),否则下一个项目是未知的。

什么可以直接实现基于生成器的lookahead看起来像?目前我使用一种解决方法,这意味着做一个列表出来发电机:

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

先行那么容易被这样的事情来实现:

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

当然,这只是正常工作。但考虑到这一点,我的第二个问题就出现了:首先是否真的有一个使发电机成为scan()的点?

+0

呃......这不仅仅是关于琴弦,是吗?考虑诸如'(_ for _ in(无,'evil',4,0))',这也是一个生成器。 – n611x007 2014-06-25 16:28:03

回答

13

你可以写一些缓冲来自发电机项目的某编号的包装,并提供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] 
+0

真的很好,既简单又灵活。我认为这个实现大多符合我想象的,谢谢。顺便说一句,我想知道如何通过Python中的扫描仪,解析器或类似的东西来处理这些问题。我已经抛出了一些Python核心库代码,比如SRE模块或者tokenizer,但是我还没有看到类似于正在使用的lookahead迭代器的东西。 – jena 2009-10-05 13:03:19

+3

虽然效率可能并不重要,但对于小型预览来说,效果可能并不重要。 – kindall 2011-06-23 04:47:16

+0

你会提供一个这样的例子吗? – kdubs 2017-01-07 21:39:28

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 
+0

'next'是一个Python内建函数。 – 2009-10-05 02:20:46

+0

对不起,我还在想Python3以前的版本!改为改为next_token。 – PaulMcG 2009-10-05 05:12:24

+0

scan()可以被内建函数替换() – NicDumZ 2009-10-05 06:31:25

0

Paul's是一个很好的答案。任意超前一类为基础的方法可能看起来像:

class lookahead(object): 
    def __init__(self, generator, lookahead_count=1): 
     self.gen = iter(generator) 
     self.look_count = lookahead_count 

    def __iter__(self): 
     self.lookahead = [] 
     self.stopped = False 
     try: 
      for i in range(self.look_count): 
       self.lookahead.append(self.gen.next()) 
     except StopIteration: 
      self.stopped = True 
     return self 

    def next(self): 
     if not self.stopped: 
      try: 
       self.lookahead.append(self.gen.next()) 
      except StopIteration: 
       self.stopped = True 
     if self.lookahead != []: 
      return self.lookahead.pop(0) 
     else: 
      raise StopIteration 

x = lookahead("abcdef", 3) 
for i in x: 
    print i, x.lookahead 
3

下面是一个例子,它允许一个单一的项目将被送回发电机

def gen(): 
    for i in range(100): 
     v=yield i   # when you call next(), v will be set to None 
     if v: 
      yield None  # this yields None to send() call 
      v=yield v  # so this yield is for the first next() after send() 

g=gen() 

x=g.next() 
print 0,x 

x=g.next() 
print 1,x 

x=g.next() 
print 2,x # oops push it back 

x=g.send(x) 

x=g.next() 
print 3,x # x should be 2 again 

x=g.next() 
print 4,x 
21

还不错的答案在那里,但我最喜欢方法是使用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,只是提供这个薄胶水!)

+1

伟大的代码。请注意,实现'__next __()'给了我“TypeError:无法使用下一个抽象方法实例化抽象类IteratorWithLookahead”。将方法名更改为'next()'解决了这个问题。 (CPython 2.7) – bavaza 2013-12-18 15:32:52

+1

@bavaza它需要在Python 3上是'__next__',在Python 2上是'next'。 – gsnedders 2015-03-03 17:43:25

+0

我只为我的代码库包含'next'和'__next__'。 – AlexLordThorsen 2016-02-23 05:17:58

1

既然你说你是标记一个字符串而不是一般的迭代,我建议最简单的解决方案,返回一个3元组: (token_type, token_value, token_index),其中token_index是该字符串中令牌的索引。然后你可以向前看,向后看,或者在字符串中的任何其他地方。只是不要结束。我认为最简单和最灵活的解决方案。

此外,您不需要使用列表理解从生成器创建列表。只需调用列表()构造函数就可以了:

token_list = list(scan(string)) 
+0

这是一个非常有趣的想法,因为它首先避免了这个问题。但我认为有两个缺点:首先,如果从令牌流访问令牌的部分与扫描器不同,则必须提供令牌流和原始字符串。 但是,我可以忍受这一点,无论如何让扫描仪执行访问工作可能是一个好主意。 但我认为通过使用原始字符串来查看令牌只能提供值,但不会提供其他类似于令牌类型的注释类型,这在某些情况下可能是必不可少的(所以在我的情况下)。 – jena 2009-10-05 12:50:15

0

我多么简洁写,如果我只需要向前看的1元的价值:

SEQUENCE_END = object() 

def lookahead(iterable): 
    iter = iter(iterable) 
    current = next(iter) 
    for ahead in iter: 
     yield current,ahead 
     current = ahead 
    yield current,SEQUENCE_END 

例子:

>>> for x,ahead in lookahead(range(3)): 
>>>  print(x,ahead) 
0, 1 
1, 2 
2, <object SEQUENCE_END> 
2

使用itertools.tee构建简单的超前包装:

from itertools import tee, islice 

class LookAhead: 
    'Wrap an iterator with lookahead indexing' 
    def __init__(self, iterator): 
     self.t = tee(iterator, 1)[0] 
    def __iter__(self): 
     return self 
    def next(self): 
     return next(self.t) 
    def __getitem__(self, i): 
     for value in islice(self.t.__copy__(), i, None): 
      return value 
     raise IndexError(i) 

使用该类来包装现有的迭代器或迭代器。然后,您可以使用下一个正常迭代,或者您可以使用索引查找进行前瞻。

>>> it = LookAhead([10, 20, 30, 40, 50]) 
>>> next(it) 
10 
>>> it[0] 
20 
>>> next(it) 
20 
>>> it[0] 
30 
>>> list(it) 
[30, 40, 50] 

到Python 3下运行该代码,只需将方法改变为__next__