89

Python ジェネレーターで 1 つの要素を先読みする方法がわかりません。見たらすぐなくなってる。

これが私が意味することです:

gen = iter([1,2,3])
next_value = gen.next()  # okay, I looked forward and see that next_value = 1
# but now:
list(gen)  # is [2, 3]  -- the first value is gone!

より実際の例を次に示します。

gen = element_generator()
if gen.next_value() == 'STOP':
  quit_application()
else:
  process(gen.next())

1つの要素を先読みできるジェネレーターを書くのを手伝ってくれる人はいますか?

4

17 に答える 17

91

完全を期すために、more-itertoolsパッケージ(おそらく Python プログラマーのツールボックスの一部である必要があります) には、peekableこの動作を実装するラッパーが含まれています。ドキュメントのコード例が示すように:

>>> p = peekable(['a', 'b'])
>>> p.peek()
'a'
>>> next(p)
'a'

ただし、この機能を実際に必要としないように、この機能を使用するコードを書き直すことができる場合がよくあります。たとえば、質問の現実的なコード サンプルは次のように記述できます。

gen = element_generator()
command = gen.next_value()
if command == 'STOP':
  quit_application()
else:
  process(command)

(読者のメモ: 古いバージョンの Python を参照しているにもかかわらず、これを書いている時点での質問の例の構文を保持しています)

于 2014-12-30T01:59:05.340 に答える
67

Python ジェネレーター API は 1 つの方法です。読み取った要素をプッシュバックすることはできません。ただし、 itertools モジュールを使用して新しいイテレータを作成し、要素を先頭に追加できます。

import itertools

gen = iter([1,2,3])
peek = gen.next()
print list(itertools.chain([peek], gen))
于 2010-03-11T13:46:09.893 に答える
24

わかりました-2年遅すぎます-しかし、私はこの質問に出くわし、満足のいく答えを見つけることができませんでした。このメタジェネレーターを思いついた:

class Peekorator(object):

    def __init__(self, generator):
        self.empty = False
        self.peek = None
        self.generator = generator
        try:
            self.peek = self.generator.next()
        except StopIteration:
            self.empty = True

    def __iter__(self):
        return self

    def next(self):
        """
        Return the self.peek element, or raise StopIteration
        if empty
        """
        if self.empty:
            raise StopIteration()
        to_return = self.peek
        try:
            self.peek = self.generator.next()
        except StopIteration:
            self.peek = None
            self.empty = True
        return to_return

def simple_iterator():
    for x in range(10):
        yield x*3

pkr = Peekorator(simple_iterator())
for i in pkr:
    print i, pkr.peek, pkr.empty

結果:

0 3 False
3 6 False
6 9 False
9 12 False    
...
24 27 False
27 None False

つまり、反復中はいつでもリスト内の次のアイテムにアクセスできます。

于 2012-05-14T01:18:25.507 に答える
6

楽しみのために、アーロンの提案に基づいて先読みクラスの実装を作成しました。

import itertools

class lookahead_chain(object):
    def __init__(self, it):
        self._it = iter(it)

    def __iter__(self):
        return self

    def next(self):
        return next(self._it)

    def peek(self, default=None, _chain=itertools.chain):
        it = self._it
        try:
            v = self._it.next()
            self._it = _chain((v,), it)
            return v
        except StopIteration:
            return default

lookahead = lookahead_chain

これにより、以下が機能します。

>>> t = lookahead(xrange(8))
>>> list(itertools.islice(t, 3))
[0, 1, 2]
>>> t.peek()
3
>>> list(itertools.islice(t, 3))
[3, 4, 5]

この実装では、 peek を何度も連続して呼び出すのは悪い考えです...

CPython のソース コードを見ていると、より短くて効率的な方法が見つかりました。

class lookahead_tee(object):
    def __init__(self, it):
        self._it, = itertools.tee(it, 1)

    def __iter__(self):
        return self._it

    def peek(self, default=None):
        try:
            return self._it.__copy__().next()
        except StopIteration:
            return default

lookahead = lookahead_tee

使い方は上記と同じですが、peek を連続して何度も使用するためにここで料金を支払う必要はありません。さらに数行追加すると、イテレータで複数の項目を先読みすることもできます (利用可能な RAM まで)。

于 2013-12-05T22:57:58.770 に答える
5
>>> gen = iter(range(10))
>>> peek = next(gen)
>>> peek
0
>>> gen = (value for g in ([peek], gen) for value in g)
>>> list(gen)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
于 2013-01-28T07:16:25.913 に答える
3

これは機能します-アイテムをバッファリングし、各アイテムとシーケンス内の次のアイテムで関数を呼び出します。

あなたの要件は、シーケンスの最後に何が起こるかについて曖昧です。あなたが最後にいるとき、「先を見越す」とはどういう意味ですか?

def process_with_lookahead( iterable, aFunction ):
    prev= iterable.next()
    for item in iterable:
        aFunction( prev, item )
        prev= item
    aFunction( item, None )

def someLookaheadFunction( item, next_item ):
    print item, next_item
于 2010-03-11T13:53:19.010 に答える
3

アイテム (i, i+1) を使用する代わりに、'i' が現在のアイテムで i+1 が 'peek ahead' バージョンである場合、'i-1' である (i-1, i) を使用する必要があります。ジェネレーターの以前のバージョンです。

このようにアルゴリズムを微調整すると、現在持っているものと同じものが生成されます。

先をのぞくのは間違いであり、そうすべきではありません。

于 2012-08-21T17:08:39.807 に答える
1

私の場合、 next() 呼び出しで取得したばかりのデータを生成するためにキューに戻すことができるジェネレーターが必要です。

この問題を処理する方法は、キューを作成することです。ジェネレーターの実装では、最初にキューをチェックします。キューが空でない場合、「yield」はキュー内の値を返します。それ以外の場合は、通常の方法で値を返します。

import queue


def gen1(n, q):
    i = 0
    while True:
        if not q.empty():
            yield q.get()
        else:
            yield i
            i = i + 1
            if i >= n:
                if not q.empty():
                    yield q.get()
                break


q = queue.Queue()

f = gen1(2, q)

i = next(f)
print(i)
i = next(f)
print(i)
q.put(i) # put back the value I have just got for following 'next' call
i = next(f)
print(i)

ランニング

python3 gen_test.py
0
1
1

この概念は、ファイルを 1 行ずつ調べる必要があるパーサーを作成するときに非常に役立ちます。その行が解析の次のフェーズに属しているように見える場合は、ジェネレーターにキューを戻して、コードの次のフェーズを実行できるようにすることができます。複雑な状態を処理せずに正しく解析します。

于 2021-09-10T08:45:07.313 に答える
1

cytoolzにはピーク機能があります。

>> from cytoolz import peek
>> gen = iter([1,2,3])
>> first, continuation = peek(gen)
>> first
1
>> list(continuation)
[1, 2, 3]
于 2019-06-02T23:56:05.580 に答える
0

ここitertools.chain()での仕事の自然なツールですが、次のようなループに注意してください。

for elem in gen:
    ...
    peek = next(gen)
    gen = itertools.chain([peek], gen)

...これは直線的に増加する量のメモリを消費し、最終的に停止するためです。(このコードは基本的に、chain() 呼び出しごとに 1 つのノードで、リンクされたリストを作成するようです。) これは、ライブラリを調べたからではなく、プログラムの速度が大幅に低下したためですgen = itertools.chain([peek], gen)。また。(パイソン3.3)

于 2015-02-05T20:37:58.460 に答える