私の知る限り、例外を監視するとプログラムが遅くなります。
ループを遅くStopIteration
するなど、反復子の例外を監視しますか?for
私の知る限り、例外を監視するとプログラムが遅くなります。
ループを遅くStopIteration
するなど、反復子の例外を監視しますか?for
通常の場合、例外監視には多少のオーバーヘッドがありますが、イテレータの場合、例外処理に関連するオーバーヘッドはないよう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
繰り返しになりますが、イテレータまたはジェネレータを処理するための特定のオーバーヘッドはありません ( range
Python が処理する方法によるものではなく、組み込みであるため、ジェネレータ バージョンよりも最適化されている可能性があります)。
最後に、で書かれた実際の明示的なイテレータを見てみましょう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 を再利用すると混乱を招く可能性があります。これは、ループを途中で終了することによってマスクされる本物のエラーである可能性があるためです。