1. ホーム
  2. python

[解決済み] Python: ジェネレータ式とyieldの比較

2023-02-14 08:43:50

質問

Pythonでは、ジェネレータオブジェクトを ジェネレータ式 を使うのと 降伏 ステートメントを使うか?

使用方法 降伏 :

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)などのタプルを生成するジェネレータオブジェクトを返します。

どちらか一方の利点はありますか?感想は?

どのように解決するのですか?

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

編集してください。 私の最初のバージョンは、対話型プロンプトのmodule-scopeで作成されたジェネレータ式をデコンパイルしました。 これは、関数内で使用されているOPのバージョンとは若干異なります。 質問の実際のケースと一致するようにこれを修正しました。

下記でわかるように、"yield"ジェネレータ(最初のケース)にはセットアップで3つの余分な命令がありますが、1番目から FOR_ITER を使用します。 LOAD_FAST の代わりに LOAD_DEREF に置き換えます。 その LOAD_DEREF "むしろ遅くなる"。 よりも LOAD_FAST の値が十分に大きい場合、ジェネレータ式よりも "yield"版の方がわずかに速くなります。 x (外側ループ) の値は y の値は各パスでわずかに速くロードされるからです。 より小さい値の x の値が小さい場合は、設定コードのオーバーヘッドが増えるため、わずかに遅くなります。

ジェネレータ式は通常、関数でラップするのではなく、コード内でインラインで使用されることを指摘する価値があるかもしれません。 これにより、セットアップのオーバーヘッドを少し取り除き、ループ値が小さい場合にジェネレータ式をわずかに高速に保つことができます。 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