我在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()
成为一个发电机?
答案 0 :(得分:22)
那里有很好的答案,但我最喜欢的方法是使用itertools.tee
- 给定一个迭代器,它会返回两个(或者更多,如果请求)可以独立进行。它根据需要在内存中缓冲(即,如果迭代器彼此之间没有非常“失步”,则不会太多)。 E.g:
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 :(得分: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]
答案 2 :(得分: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
答案 3 :(得分: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
答案 4 :(得分:2)
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)
使用该类包装现有的iterable或迭代器。然后,您可以使用 next 正常迭代,也可以使用索引查找进行预测。
>>> 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 方法更改为 __ next __ 。
答案 5 :(得分:1)
既然你说你是字符串而不是一般的可迭代,我建议最简单的解决方案就是扩展你的tokenizer以返回一个3元组:
(token_type, token_value, token_index)
,其中token_index
是字符串中令牌的索引。然后,您可以向前,向后或在字符串中的任何其他位置向前看。只是不要走到尽头。我认为最简单,最灵活的解决方案。
此外,您无需使用列表推导来从生成器创建列表。只需在其上调用list()构造函数:
token_list = list(scan(string))
答案 6 :(得分:0)
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
答案 7 :(得分: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>
答案 8 :(得分:0)
您可以使用 lazysequence
,这是一个不可变序列,它包装一个可迭代对象并将消耗的项目缓存在内部缓冲区中。您可以像使用任何列表或元组一样使用它,但迭代器只会根据给定操作的需要进行高级处理。
以下是使用惰性序列的示例的外观:
from lazysequence import lazysequence
token_list = lazysequence(token for token in scan(string))
try:
next_token = token_list[index + 1]
except IndexError:
next_token = None
以下是您自己实现惰性序列的方法:
from collections.abc import Sequence
class lazysequence(Sequence):
def __init__(self, iterable):
self._iter = iter(iterable)
self._cache = []
def __iter__(self):
yield from self._cache
for item in self._iter:
self._cache.append(item)
yield item
def __len__(self):
return sum(1 for _ in self)
def __getitem__(self, index):
for position, item in enumerate(self):
if index == position:
return item
raise IndexError("lazysequence index out of range")
这是一个幼稚的实现。这里缺少的一些东西:
if s
) 中,评估整个序列,而不仅仅是第一项。len(s)
和 s[i]
需要遍历序列,即使项目已经存储在内部缓存中也是如此。s[-1]
) 和切片 (s[:2]
)。PyPI 包解决了这些问题,以及其他一些问题。最后一个警告同时适用于上述实现和包:
len(s)
产生将迭代器消耗到最后的成本。披露:我是lazysequence
的作者。