9

2 つのリストを作成して圧縮すると

a=[1,2,3]
b=[7,8,9]
z=zip(a,b)

次に、 z を 2 つのリストに型キャストします

l1=list(z)
l2=list(z)

次に、l1 の内容は [(1,7),(2,8),(3,9)] であることがわかりますが、l2 の内容は単に [] です。

これは、イテラブルに関する python の一般的な動作だと思います。しかし、C ファミリーから移行する初心者のプログラマーとして、これは私には意味がありません。なぜそのような振る舞いをするのでしょうか。そして、この問題を乗り越える方法はありますか?

つまり、この特定の例では、l1 を l2 にコピーするだけで済みますが、一般に、Python が 'z' を 1 回反復した後に反復するために使用するものを「リセット」する方法はありますか?

4

5 に答える 5

12

ジェネレーターを「リセット」する方法はありません。ただし、イテレータを「コピー」するために使用できます。itertools.tee

>>> z = zip(a, b)
>>> zip1, zip2 = itertools.tee(z)
>>> list(zip1)
[(1, 7), (2, 8), (3, 9)]
>>> list(zip2)
[(1, 7), (2, 8), (3, 9)]

これには値のキャッシュが含まれるため、両方のイテラブルをほぼ同じ速度で反復する場合にのみ意味があります。(言い換えれば、私がここに持っている方法でそれを使用しないでください!)

もう 1 つの方法は、ジェネレーター関数を渡し、繰り返したいときにいつでも呼び出すことです。

def gen(x):
    for i in range(x):
        yield i ** 2

def make_two_lists(gen):
    return list(gen()), list(gen())

ただし、引数を渡すときにジェネレータ関数に引数をバインドする必要があります。あなたはそれを使うことができますlambdaが、多くの人はlambda醜いと思います. (私ではありません!YMMV。)

>>> make_two_lists(lambda: gen(10))
([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], [0, 1, 4, 9, 16, 25, 36, 49, 64, 81])

言うまでもなく、ほとんどの場合、リストを作成してコピーする方がよいでしょう。

また、この動作を説明するより一般的な方法として、これを考慮してください。ジェネレーターのポイントは、反復間で何らかの状態を維持しながら、一連の値を生成することです。ここで、単にジェネレーターを反復処理する代わりに、次のようなことをしたい場合があります。

z = zip(a, b)
while some_condition():
    fst = next(z, None)
    snd = next(z, None)
    do_some_things(fst, snd)
    if fst is None and snd is None:
        do_some_other_things()

このループが尽きるかもしれないし、尽きないかもしれないとしましょうz。これで、不確定な状態のジェネレーターができました! したがって、この時点で、ジェネレーターの動作が明確に定義された方法で制限されていることが重要です。ジェネレーターが出力のどこにあるかはわかりませんが、a) 後続のすべてのアクセスがシリーズのの値を生成し、b) 一度「空」になると、シリーズのすべての項目を正確に取得したことはわかっています。一度。の状態を操作する能力があればzあるほど、それを推論するのが難しくなるため、これら 2 つの約束を破る状況は避けるのが最善です。

もちろん、Joel Cornett が以下で指摘しているように、メソッドを介してメッセージを受け取るジェネレーターを作成することは可能です。sendを使用してリセットできるジェネレーターを作成することも可能sendです。ただし、その場合、できることはメッセージを送信することだけであることに注意してください。ジェネレーターの状態を直接操作することはできないため、ジェネレーターの状態に対するすべての変更は (ジェネレーター自体によって、正しく記述されていると仮定して) 明確に定義されています。send本当にコルーチンを実装するためのものなので、この目的には使用しません。毎日のジェネレーターは、送信された値で何もすることはほとんどありません-私が上で示したまさにその理由によると思います。

于 2012-06-02T21:41:04.010 に答える
3

リストのコピーが 2 つ必要な場合 (それらを変更する必要がある場合) は、リストを一度作成してからコピーすることをお勧めします。

a=[1,2,3]
b=[7,8,9]
l1 = list(zip(a,b))
l2 = l1[:]
于 2012-06-03T07:13:05.357 に答える
2

list()一度 使用してイテレータからリストを作成し、後でそれを使用するだけです。

たまたま、一度だけ反復できるイテレータであるgeneratorzipを返します。

リストは何度でも繰り返すことができます。

于 2012-06-02T21:39:23.230 に答える
1

いいえ、「リセット」する方法はありません。

ジェネレーターは、オンデマンドで出力を 1 つずつ生成し出力が使い果たされると終了します。

ファイルを読み取るようなものだと考えてください。一度読み終えたら、もう一度データを読みたい場合は再起動する必要があります。

ジェネレーターの出力を保持する必要がある場合は、たとえばリストに格納し、必要に応じて再利用することを検討してください。( v2 でメモリ内に項目の全リストを作成するxrange()ジェネレーター vsの使用を導いた決定にいくぶん似ています)range()

更新: 用語の修正、一時的な脳機能停止 ...

于 2012-06-02T21:38:21.630 に答える
0

さらに別の説明。プログラマーとして、おそらくクラスとインスタンス (つまりオブジェクト) の違いを理解しているでしょう。は組み込み関数でzip()あると言われています(公式ドキュメント)。実際には、組み込みのジェネレーター関数です。それはむしろクラスであることを意味します。インタラクティブ モードで試すこともできます。

>>> zip
<class 'zip'>

クラスは型です。そのため、次のことも明確にする必要があります。

>>> type(zip)
<class 'type'>

Yourはクラスのインスタンスであり、クラス コンストラクターの呼び出しについて as をz呼び出すことを考えることができます。zip()

>>> a = [1, 2, 3]
>>> b = [7, 8, 9]
>>> z = zip(a, b)
>>> z
<zip object at 0x0000000002342AC8>
>>> type(z)
<class 'zip'>

は、およびzの反復子の内部に保持される反復子 (オブジェクト) です。その一般的な実装のため、(またはクラス) はorまたは any シーケンスを介してイテレータをリセットする手段がありません。そのため、をリセットする方法はありません。具体的な問題を解決する最もクリーンな方法は、リストをコピーすることです (質問で述べたように、Lennart Regebro が示唆しているように)。もう 1 つのわかりやすい方法は、two を使用して、最初から同じように動作する 2 つの似たイテレータを構築することです。abzzipabzzip(a, b)z

>>> lst1 = list(zip(a, b))
>>> lst2 = list(zip(a, b))

ただし、これは一般的に同じ結果では使用できません。いくつかの現在の条件 (複数の温度計から読み取った温度など) に基づいて生成された一意のシーケンスについて考えてみてくださいab

于 2012-06-03T09:15:58.317 に答える