94

Python では、ジェネレーター式を使用してジェネレーター オブジェクトを作成する場合と、 yieldステートメントを使用する場合に違いはありますか?

yield の使用:

def Generator(x, y):
    for i in xrange(x):
        for j in xrange(y):
            yield(i, j)

ジェネレーター式の使用:

def Generator(x, y):
    return ((i, j) for i in xrange(x) for j in xrange(y))

どちらの関数も、(0,0)、(0,1) などのタプルを生成するジェネレーター オブジェクトを返します。

どちらかの利点はありますか?考え?

4

8 に答える 8

75

両者にはわずかな違いしかありません。モジュールを使用して、disこの種のことを自分で調べることができます。

編集:私の最初のバージョンでは、インタラクティブ プロンプトでモジュール スコープで作成されたジェネレータ式を逆コンパイルしました。これは、関数内で使用される OP のバージョンとは少し異なります。質問の実際のケースに一致するようにこれを変更しました。

以下に示すように、"yield" ジェネレーター (最初のケース) にはセットアップに 3 つの追加の命令がありますが、最初FOR_ITERとは 1 つの点だけが異なります。"yield" アプローチでは、ループ内LOAD_FASTで a の代わりに in を使用します。LOAD_DEREFはよりもLOAD_DEREFかなり遅い」ため、 (外側のループ) のLOAD_FAST値が十分に大きい場合、ジェネレーター式よりも「yield」バージョンがわずかに速くなります。小さい値の場合、セットアップ コードの余分なオーバーヘッドが原因で、少し遅くなります。xyx

ジェネレーター式は通常、そのような関数でラップするのではなく、コード内でインラインで使用されることも指摘しておく価値があるかもしれません。これにより、セットアップのオーバーヘッドが少し取り除かれLOAD_FAST、「yield」バージョンに利点が与えられたとしても、ループ値が小さい場合にジェネレータ式がわずかに速くなります。

どちらの場合も、パフォーマンスの違いは、どちらか一方の決定を正当化するのに十分ではありません. 読みやすさははるかに重要なので、目の前の状況で最も読みやすいと思われるものを使用してください。

>>> def Generator(x, y):
...     for i in xrange(x):
...         for j in xrange(y):
...             yield(i, j)
...
>>> dis.dis(Generator)
  2           0 SETUP_LOOP              54 (to 57)
              3 LOAD_GLOBAL              0 (xrange)
              6 LOAD_FAST                0 (x)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                40 (to 56)
             16 STORE_FAST               2 (i)

  3          19 SETUP_LOOP              31 (to 53)
             22 LOAD_GLOBAL              0 (xrange)
             25 LOAD_FAST                1 (y)
             28 CALL_FUNCTION            1
             31 GET_ITER
        >>   32 FOR_ITER                17 (to 52)
             35 STORE_FAST               3 (j)

  4          38 LOAD_FAST                2 (i)
             41 LOAD_FAST                3 (j)
             44 BUILD_TUPLE              2
             47 YIELD_VALUE
             48 POP_TOP
             49 JUMP_ABSOLUTE           32
        >>   52 POP_BLOCK
        >>   53 JUMP_ABSOLUTE           13
        >>   56 POP_BLOCK
        >>   57 LOAD_CONST               0 (None)
             60 RETURN_VALUE
>>> def Generator_expr(x, y):
...    return ((i, j) for i in xrange(x) for j in xrange(y))
...
>>> dis.dis(Generator_expr.func_code.co_consts[1])
  2           0 SETUP_LOOP              47 (to 50)
              3 LOAD_FAST                0 (.0)
        >>    6 FOR_ITER                40 (to 49)
              9 STORE_FAST               1 (i)
             12 SETUP_LOOP              31 (to 46)
             15 LOAD_GLOBAL              0 (xrange)
             18 LOAD_DEREF               0 (y)
             21 CALL_FUNCTION            1
             24 GET_ITER
        >>   25 FOR_ITER                17 (to 45)
             28 STORE_FAST               2 (j)
             31 LOAD_FAST                1 (i)
             34 LOAD_FAST                2 (j)
             37 BUILD_TUPLE              2
             40 YIELD_VALUE
             41 POP_TOP
             42 JUMP_ABSOLUTE           25
        >>   45 POP_BLOCK
        >>   46 JUMP_ABSOLUTE            6
        >>   49 POP_BLOCK
        >>   50 LOAD_CONST               0 (None)
             53 RETURN_VALUE
于 2010-01-03T17:17:12.320 に答える
36

この例では、そうではありません。ただしyield、より複雑な構造に使用できます。たとえば、呼び出し元からの値も受け入れ、結果としてフローを変更できます。詳細については、 PEP 342を参照してください (これは、知っておく価値のある興味深い手法です)。

とにかく、最善のアドバイスは、ニーズに合ったものを使用することです。

PS Dave Beazleyの簡単なコルーチンの例を次に示します。

def grep(pattern):
    print "Looking for %s" % pattern
    while True:
        line = (yield)
        if pattern in line:
            print line,

# Example use
if __name__ == '__main__':
    g = grep("python")
    g.next()
    g.send("Yeah, but no, but yeah, but no")
    g.send("A series of tubes")
    g.send("python generators rock!")
于 2010-01-03T16:13:33.807 に答える
19

ジェネレータ式に適合できる単純なループの種類に違いはありません。ただし、yieldを使用して、はるかに複雑な処理を行うジェネレーターを作成できます。フィボナッチ数列を生成する簡単な例を次に示します。

>>> def fibgen():
...    a = b = 1
...    while True:
...        yield a
...        a, b = b, a+b

>>> list(itertools.takewhile((lambda x: x<100), fibgen()))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
于 2010-01-03T16:30:39.633 に答える
10

使用時には、ジェネレーター オブジェクトとジェネレーター関数の違いに注意してください。

ジェネレーター オブジェクトは、新しいジェネレーター オブジェクトを返すため、もう一度呼び出すたびに再利用できるジェネレーター関数とは対照的に、一度だけ使用できます。

ジェネレーター式は、実際には通常、関数でラップせずに「そのまま」使用され、ジェネレーター オブジェクトを返します。

例えば:

def range_10_gen_func():
    x = 0
    while x < 10:
        yield x
        x = x + 1

print(list(range_10_gen_func()))
print(list(range_10_gen_func()))
print(list(range_10_gen_func()))

出力:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

わずかに異なる使用法と比較してください。

range_10_gen = range_10_gen_func()
print(list(range_10_gen))
print(list(range_10_gen))
print(list(range_10_gen))

出力:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]

ジェネレーター式と比較します。

range_10_gen_expr = (x for x in range(10))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))
print(list(range_10_gen_expr))

また、出力:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[]
[]
于 2010-01-12T06:26:29.073 に答える
8

yieldネストされたループよりも式が複雑な場合は、使用すると便利です。特に、特別な最初または特別な最後の値を返すことができます。検討:

def Generator(x):
  for i in xrange(x):
    yield(i)
  yield(None)
于 2010-01-03T16:13:13.650 に答える
5

イテレータについて考えるとき、itertoolsモジュールは:

...単独でも組み合わせても便利な、高速でメモリ効率の高いツールのコア セットを標準化します。これらは一緒になって「イテレータ代数」を形成し、純粋な Python で特殊なツールを簡潔かつ効率的に構築することを可能にします。

パフォーマンスについては、考慮してくださいitertools.product(*iterables[, repeat])

入力イテラブルのデカルト積。

ジェネレーター式のネストされた for ループと同等です。たとえばproduct(A, B)、 と同じものを返します((x,y) for x in A for y in B)

>>> import itertools
>>> def gen(x,y):
...     return itertools.product(xrange(x),xrange(y))
... 
>>> [t for t in gen(3,2)]
[(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]
>>> 
于 2010-01-03T16:21:44.590 に答える