例えば、あるループ中にあるシーケンスの要素を参照するとき。
for i in range(10):
value = a[i]
これだと、インデックスiが有効かどうかわからないのでどうにかしたい。
(例1)
とりあえず長さをチェックして参照する。
for i in range(10):
if i < len(a):
value = a[i]
これはループで毎回len(a)するのでいかにも遅そう。
(例2)
シーケンスの長さが変わらないのであればループの前にあらかじめ長さをとっておこう。
alen = len(a)
for i in range(10):
if i < alen:
value = a[i]
(例3)
とりあえず参照して例外を拾おう。
for i in range(10):
try:
value = a[i]
except IndexError:
pass
どれを使うのがよさげなのか計ってみよう。
テスト用スクリプト
#!/usr/bin/env python
import timeit
# インデックスiが範囲内の場合のセットアップ
setup1 = """
a = [ 1, 2, 3 ]
alen = len(a)
i = 1
"""
# インデックスiが範囲外の場合のセットアップ
setup2 = """
a = [ 1, 2, 3 ]
alen = len(a)
i = 5
"""
# 例1用ステートメント
stmt1 = """
if i < len(a):
a[i]
else:
pass
"""
# 例2用ステートメント
stmt2 = """
if i < alen:
a[i]
else:
pass
"""
# 例3用ステートメント
stmt3 = """
try:
a[i]
except IndexError:
pass
"""
print timeit.Timer(setup = setup1, stmt = stmt1).timeit()
print timeit.Timer(setup = setup1, stmt = stmt2).timeit()
print timeit.Timer(setup = setup1, stmt = stmt3).timeit()
print timeit.Timer(setup = setup2, stmt = stmt1).timeit()
print timeit.Timer(setup = setup2, stmt = stmt2).timeit()
print timeit.Timer(setup = setup2, stmt = stmt3).timeit()
実行結果
# インデックスが範囲内
0.361014127731 # 例1
0.125293016434 # 例2
0.128052949905 # 例3
# インデックスが範囲外
0.284233808517 # 例1
0.101886034012 # 例2
1.82788801193 # 例3
例外処理で実装した場合(例3)のインデックスが範囲外になる時が飛びぬけて遅い。
つまり、例外発生のコストは結構大きい。
例外発生が頻繁に発生することが予測される場合、if...elseで簡単に
代替できるのであればif...elseを使ったほうが高速。