4

次の python コードは、3 の倍数と 7 の倍数をすべて組み合わせた予想されるタプルのリストではなく、[(0, 0), (0, 7)...(0, 693)] を生成します。

multiples_of_3 = (i*3 for i in range(100))
multiples_of_7 = (i*7 for i in range(100))
list((i,j) for i in multiples_of_3 for j in multiples_of_7)

このコードは問題を修正します:

list((i,j) for i in (i*3 for i in range(100)) for j in (i*7 for i in range(100)))

質問:

  1. ジェネレーター オブジェクトは、生成されたリストが列挙されるたびにイテレーター オブジェクトを提供するのではなく、イテレーターの役割を果たしているようです。後者の戦略は、.Net LINQ クエリ オブジェクトによって採用されているようです。これを回避するエレガントな方法はありますか?
  2. 2 番目のコードが機能するのはなぜですか? 7 の倍数をすべてループした後、ジェネレーターのイテレーターがリセットされないことを理解できますか?
  3. この振る舞いは矛盾していないとしても直感に反すると思いませんか?
4

4 に答える 4

3

ジェネレーター オブジェクト反復子であるため、ワンショットです。これは、任意の数の独立した反復子を生成できるiterableではありません。この動作は、どこかのスイッチで変更できるものではないため、回避策は、ジェネレーターの代わりにイテラブル (リストなど) を使用するか、ジェネレーターを繰り返し構築することになります。

2 番目のスニペットは後者を行います。定義上、ループと同等です

for i in (i*3 for i in range(100)):
    for j in (i*7 for i in range(100)):
        ...

ここで、後者のジェネレーター式が外側のループの反復ごとに新たに評価されることは驚くべきことではありません。

于 2013-08-28T11:27:59.627 に答える
2

あなたが発見したように、ジェネレーター式によって作成されたオブジェクトはイテレーター (より正確にはgenerator-iterator ) であり、一度だけ消費されるように設計されています。リセット可能なジェネレーターが必要な場合は、単純に実際のジェネレーターを作成してループで使用します。

def multiples_of_3():               # generator
    for i in range(100):
       yield i * 3
def multiples_of_7():               # generator
    for i in range(100):
       yield i * 7
list((i,j) for i in multiples_of_3() for j in multiples_of_7())

2 番目のコードが機能するのは、内側のループ ( (i*7 ...)) の式リストが外側のループの各パスで評価されるためです。これにより、毎回新しいジェネレーターイテレーターが作成され、必要な動作が得られますが、コードの明瞭さが犠牲になります。

for何が起こっているのかを理解するために、ループがイテレータを反復するときにイテレータの「リセット」がないことを覚えておいてください。(これは機能です。このようなリセットは、大きな反復子の反復処理をバラバラに中断し、ジェネレーターでは不可能です。) 例:

multiples_of_2 = iter(xrange(0, 100, 2))  # iterator
for i in multiples_of_2:
    print i
# prints nothing because the iterator is spent
for i in multiples_of_2:
    print i

...これとは対照的に:

multiples_of_2 = xrange(0, 100, 2)        # iterable sequence, converted to iterator
for i in multiples_of_2:
    print i
# prints again because a new iterator gets created
for i in multiples_of_2:
    print i

ジェネレーター式は、呼び出されたジェネレーターと同等であるため、1 回しか反復できません。

于 2013-08-28T11:27:03.457 に答える
1

ジェネレータ式をマルチパス iterable に変換したい場合は、かなり日常的な方法で行うことができます。例えば:

class MultiPass(object):
    def __init__(self, initfunc):
        self.initfunc = initfunc
    def __iter__(self):
        return self.initfunc()

multiples_of_3 = MultiPass(lambda: (i*3 for i in range(20)))
multiples_of_7 = MultiPass(lambda: (i*7 for i in range(20)))
print list((i,j) for i in multiples_of_3 for j in multiples_of_7)

物事を定義するという観点からは、入力するのと同じくらいの量の作業です。

def multiples_of_3():
    return (i*3 for i in range(20))

ただし、ユーザーの観点からは、multiples_of_3ではなくと記述multiples_of_3()します。これは、オブジェクトがやmultiples_of_3などの他のイテラブルとポリモーフィックであることを意味します。tuplelist

タイピングの必要性lambda:は少しエレガントではありません。下位互換性を維持しながら必要なものを提供するために、「反復可能な内包表記」を言語に導入しても害はないと思います。しかし、句読点の数は非常に多く、これが 1 つの価値があるとは考えられません。

于 2013-09-07T15:40:12.993 に答える
1

私が見つけた本当の問題は、シングル パスとマルチ パスの iterable についてであり、現在、反復可能なシングル パスかマルチ パスかを判断する標準的なメカニズムがないという事実です

于 2013-08-29T11:18:52.973 に答える