Python 3.0のrangeの注意点

Python 3.0をテストしていて驚いたのが以下の例。

>>> from timeit import *
>>> t1 = Timer('L[5000]', 'L=range(10000)')
>>> t2 = Timer('L[5000]', 'L=list(range(10000))')
>>> t1.timeit()
0.61441663675145719
>>> t2.timeit()
0.1539030798605836

3.0のrangeは2.5のxrangeを意味する。2.5でもxrangeで生成したオブジェクトに対してインデクシングできたのは意識していなかったが、rangeはイテレータっぽいものだと思うのでインデクシングは遅いと思われる。ちなみに、L=range(10)としてnext(L)とすると、「TypeError: range object is not an iterator」とエラーが出るのでイテレータではないらしい。


3.0ではリストを返すrangeがなくなるので、リストのような感覚でrangeを使用していると効率の悪いコードができてしまう。例えば関数の引数でイテレータでなくリストを受け取ることを想定して実装してインデクシングなどを使用していると、rangeオブジェクトが渡された際にパフォーマンスの悪いコードになってしまう。そういう場合はlist化してから渡す必要があるかもしれないが、恐らくリストのサイズやインデクシングの回数によってはlist化するオーバーヘッドの方がより時間がかかるかもしれない。


以下に3.0のrangeでできることをまとめる。挙動としては限定的なリストっぽい。2.5と3.0で挙動は共通。

list range イテレータ
L[1] OK OK NG
L[1:3] OK NG NG
len(L) OK OK NG
next(L) NG NG OK


あと大事なポイントとしては、2.5のxrangeはint値しかとれなかったが、3.0のrangeはlong値(に2.5で相当するもの)を引数にとれるようになった。確か、3.0でのintは2.5のlongにあたり、2.5のintにあたるものは3.0ではなくなったと思う。