1

私の知る限り、例外を監視するとプログラムが遅くなります。

ループを遅くStopIterationするなど、反復子の例外を監視しますか?for

4

2 に答える 2

9

通常の場合、例外監視には多少のオーバーヘッドがありますが、イテレータの場合、例外処理に関連するオーバーヘッドはないようStopIterationです。Python はイテレータを特別なケースとして最適化するため、StopIteration例外ハンドラは含まれません。for(また、暗黙のうちに反復子を使用しないPython ループを考え出すのは難しいことも観察します---何かが欠けている可能性があります)。

以下にいくつかの例を示します。最初に組み込みrange関数と単純なforループを使用します。

Python 2.7.5
>>> import dis
>>> def x():
...   for i in range(1,11):
...     pass
...
>>> dis.dis(x)
  2           0 SETUP_LOOP              23 (to 26)
              3 LOAD_GLOBAL              0 (range)
              6 LOAD_CONST               1 (1)
              9 LOAD_CONST               2 (11)
             12 CALL_FUNCTION            2
             15 GET_ITER
        >>   16 FOR_ITER                 6 (to 25)
             19 STORE_FAST               0 (i)

  3          22 JUMP_ABSOLUTE           16
        >>   25 POP_BLOCK
        >>   26 LOAD_CONST               0 (None)
             29 RETURN_VALUE

範囲は基本的に反復子として扱われることに注意してください。

ここで、単純なジェネレーター関数を使用します。

>>> def g(x):
...   while x < 11:
...     yield x
...     x = x + 1
...
>>> def y():
...   for i in g(1):
...     pass
...
>>> dis.dis(y)
  2           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (g)
              6 LOAD_CONST               1 (1)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

  3          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE
>>> dis.dis(g)
  2           0 SETUP_LOOP              31 (to 34)
        >>    3 LOAD_FAST                0 (x)
              6 LOAD_CONST               1 (11)
              9 COMPARE_OP               0 (<)
             12 POP_JUMP_IF_FALSE       33

  3          15 LOAD_FAST                0 (x)
             18 YIELD_VALUE
             19 POP_TOP

  4          20 LOAD_FAST                0 (x)
             23 LOAD_CONST               2 (1)
             26 BINARY_ADD
             27 STORE_FAST               0 (x)
             30 JUMP_ABSOLUTE            3
        >>   33 POP_BLOCK
        >>   34 LOAD_CONST               0 (None)
             37 RETURN_VALUE

yここでは基本的に上記と同じであることに注意してください。11 という数字を参照するためx、違いは 1 つのLOAD_CONST命令です。x

>>> def q():
...   x = 1
...   while x < 11:
...     x = x + 1
...
>>> dis.dis(q)
  2           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (x)

  3           6 SETUP_LOOP              26 (to 35)
        >>    9 LOAD_FAST                0 (x)
             12 LOAD_CONST               2 (11)
             15 COMPARE_OP               0 (<)
             18 POP_JUMP_IF_FALSE       34

  4          21 LOAD_FAST                0 (x)
             24 LOAD_CONST               1 (1)
             27 BINARY_ADD
             28 STORE_FAST               0 (x)
             31 JUMP_ABSOLUTE            9
        >>   34 POP_BLOCK
        >>   35 LOAD_CONST               0 (None)
             38 RETURN_VALUE

繰り返しになりますが、イテレータまたはジェネレータを処理するための特定のオーバーヘッドはありません ( rangePython が処理する方法によるものではなく、組み込みであるため、ジェネレータ バージョンよりも最適化されている可能性があります)。

最後に、で書かれた実際の明示的なイテレータを見てみましょうStopIteration

>>> class G(object):
...   def __init__(self, x):
...     self.x = x
...   def __iter__(self):
...     return self
...   def next(self):
...     x = self.x
...     if x >= 11:
...       raise StopIteration
...     x = x + 1
...     return x - 1
...
>>> dis.dis(G.next)
  7           0 LOAD_FAST                0 (self)
              3 LOAD_ATTR                0 (x)
              6 STORE_FAST               1 (x)

  8           9 LOAD_FAST                1 (x)
             12 LOAD_CONST               1 (11)
             15 COMPARE_OP               5 (>=)
             18 POP_JUMP_IF_FALSE       30

  9          21 LOAD_GLOBAL              1 (StopIteration)
             24 RAISE_VARARGS            1
             27 JUMP_FORWARD             0 (to 30)

 10     >>   30 LOAD_FAST                1 (x)
             33 LOAD_CONST               2 (1)
             36 BINARY_ADD
             37 STORE_FAST               1 (x)

 11          40 LOAD_FAST                1 (x)
             43 LOAD_CONST               2 (1)
             46 BINARY_SUBTRACT
             47 RETURN_VALUE

ここで、ジェネレーター関数に含まれる命令は、この単純な反復子よりも数少ないことがわかります。これは主に、実装の違いと、StopIteration例外の発生に関連するいくつかの命令に関連しています。それにもかかわらず、この反復子を使用する関数はy上記とまったく同じです。

>>> def z():
...   for i in G(1):
...     pass
...
>>> dis.dis(z)
  2           0 SETUP_LOOP              20 (to 23)
              3 LOAD_GLOBAL              0 (G)
              6 LOAD_CONST               1 (1)
              9 CALL_FUNCTION            1
             12 GET_ITER
        >>   13 FOR_ITER                 6 (to 22)
             16 STORE_FAST               0 (i)

  3          19 JUMP_ABSOLUTE           13
        >>   22 POP_BLOCK
        >>   23 LOAD_CONST               0 (None)
             26 RETURN_VALUE

もちろん、これらの結果は、Python の for ループが反復子を最適化して、StopIteration例外の明示的なハンドラーの必要性を排除するという事実に基づいています。結局、StopIteration例外は基本的に、Python の for ループの操作の通常の部分を形成します。


このように実装されている理由については、反復子を定義するPEP-234を参照してください。これは、例外の費用の問題に具体的に対処しています。

  • 反復の終了を通知する例外が高すぎないかどうかは疑問視されています。StopIteration 例外のいくつかの代替案が提案されています: 特別な値 End で終了を通知し、関数 end() で反復子が終了したかどうかをテストし、IndexError 例外を再利用することさえあります。

    • 特別な値には、シーケンスにその特別な値が含まれている場合、そのシーケンスのループが警告なしに途中で終了するという問題があります。null で終わる C 文字列の経験から、これが引き起こす可能性のある問題が分からない場合は、特別な End 値が組み込みの名前で!

    • end() 関数を呼び出すには、反復ごとに 2 回の呼び出しが必要です。2 回の呼び出しは、1 回の呼び出しと例外のテストよりもはるかにコストがかかります。特にタイム クリティカルな for ループは、非常に安価に例外をテストできます。

    • IndexError を再利用すると混乱を招く可能性があります。これは、ループを途中で終了することによってマスクされる本物のエラーである可能性があるためです。

于 2013-11-21T09:23:02.210 に答える