19

文字列を形式(トークンの種類、トークンの値)のタプルにトークン化するジェネレーター ベースのスキャナーを Python に実装しました。

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

印刷します

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

次のタスクは、トークン ストリームの解析を意味します。そのためには、ポインターを先に動かさずに、現在のアイテムから 1 つ先のアイテムを参照できるようにする必要があります。イテレーターとジェネレーターはアイテムの完全なシーケンスを一度に提供するのではなく、必要に応じて各アイテムを提供するという事実は、次のアイテム__next__()が呼び出されない限り、リストと比較して先読みを少しトリッキーにします。

ジェネレーターベースの先読みの簡単な実装はどのようになりますか? 現在、ジェネレーターからリストを作成することを意味する回避策を使用しています。

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

先読みは、次のようなもので簡単に実装できます。

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

もちろん、これだけでうまくいきます。scan()しかし、それを考えると、私の 2 番目の疑問が生じます。そもそもジェネレーターを作成する意味は本当にあるのでしょうか?

4

9 に答える 9

22

Pretty good answers there, but my favorite approach would be to use itertools.tee -- given an iterator, it returns two (or more if requested) that can be advanced independently. It buffers in memory just as much as needed (i.e., not much, if the iterators don't get very "out of step" from each other). 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)

You can wrap any iterator with this class, and then use the .lookahead attribute of the wrapper to know what the next item to be returned in the future will be. I like to leave all the real logic to itertools.tee and just provide this thin glue!-)

于 2009-10-05T03:00:18.917 に答える
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]
于 2009-10-05T02:03:46.257 に答える
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
于 2009-10-05T01:23:56.937 に答える
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
于 2009-10-05T02:10:48.977 に答える
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)

このクラスを使用して、既存の iterable または iterator をラップします。次に、 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__に変更します。

于 2013-03-31T04:28:58.983 に答える
1

一般的な iterable ではなく文字列をトークン化していると言うので、トークナイザーを拡張して 3-tuple: を返す最も簡単な解決策をお勧めします 。(token_type, token_value, token_index)ここtoken_indexで、 は文字列内のトークンのインデックスです。次に、前方、後方、または文字列内の任意の場所を検索できます。最後まで行かないでください。私が考える最もシンプルで最も柔軟なソリューション。

また、ジェネレーターからリストを作成するためにリスト内包表記を使用する必要はありません。その上で list() コンストラクターを呼び出すだけです。

 token_list = list(scan(string))
于 2009-10-05T04:25:33.753 に答える
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>
于 2011-06-23T04:10:21.003 に答える
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
于 2009-10-05T02:04:01.730 に答える