range()
恥ずかしながら、今までrange()はリストを返すものだと勘違いしていました。
Python 2系
私が開発環境として準備したPython 2.7でrange()を試してみました。以下にそれを示します。
>>> a = range(0, 10)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> type(a)
<type 'list'>
このようにPython 2系では、range()はlist型を返してきます。これがPython 3系でも同様に成り立っていると勘違いしていました。
Python 3系
では私がメインの開発環境として使用しているPython 3.7で同様のことを実行してみましょう。
>>> a = range(0, 10)
>>> a
range(0, 10)
>>> type(a)
<class 'range'>
このようにPython 3系ではrange()はrange型という、イテラブルなオブジェクトを返します。[0, 1, ..., 9]のような戻り値をどうしても取り出したい場合は、forループで取り出すか、リストなどのシーケンスに変換する必要があります。
>>> list(a)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> tuple(a)
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
勘違いの原因: len()
len()は引数にリスト型などのシーケンスをとると思っていたのです。ところが
>>> a = range(0, 10)
>>> len(a)
10
のようにrangeオブジェクトの長さも平気で計算して返してきます。しかし、よくよく調べてみれば(というよりもすでに私自身、意識せずとも使っていたのですが)、len()は様々なオブジェクトのサイズを返すことができる万能な関数だったのです。
当たり前のようにforループで使っていましたが、まさかこんな落とし穴があったとは...まだまだPythonの奥深さを勉強していく必要がありそうです。
追記: rangeオブジェクトの要素へのアクセス
なんとこのrangeオブジェクトは要素へのアクセスも可能でした。これも私の勘違いに拍車をかけた要因でした。
>>> a = range(0, 5)
>>> a[0]
0
ただし、スライスを行うと以下のようにrangeオブジェクトのまま返ってきます。
>>> a[1:3]
range(1, 3)