range型は、数値のイミュータブル(=変更不可能なオブジェクト)なシーケンスを表します。for ループにおいて特定の回数のループに使う場合に便利です。
本記事では、range関数の使いかたの基本や特徴などについてまとめます。
確認した環境
- OS: Ubuntu20.04LTS
- Python3.11.2
range()の使い方
書式は下記の通りです。
range(stop) # 引数が一つの場合
range(start, stop[, step]) # 引数が複数の場合
引数はそれぞれ以下の通り。(スライスと同じ)
- start : 開始の数値。省略した場合は0(ゼロ)
- stop : 終了の数値。設定した値そのものは含まない(スライスと同様)
- step : いくつ置きに数値を生成するか。省略した場合はstep=1
返り値は、rangeオブジェクトです。
# range型
>>> a = range(10)
>>> type(a)
<class 'range'>
# 返り値はrangeオブジェクト
>>> a
range(0, 10)
<参考>公式リファレンス:https://docs.python.org/ja/3.11/library/stdtypes.html#ranges
具体例
具体例を以下に示します。
何らかの処理を特定回数繰り返す
“Good!”を5回表示させたい場合
>>> for i in range(5):
... print("Good!")
...
Good!
Good!
Good!
Good!
Good!
数値のシーケンスを生成する
rangeオブジェクトはイテラブルなオブジェクトなので、例えばlist()に渡すことでリストへ変換出来ます。
下記に様々なケースの具体例を示します。
0から任意の数値まで1づつ増加する数列 (r[0]=0、r[i+1] = r[i]+1)
引数stopを設定すると、0から値stopまで1stepずつ増加する数列を生成できます。
※引数stopは設定した値そのものは含まないことに注意!(スライスの指定と同じ)
>>> list(range(5))
[0, 1, 2, 3, 4]
任意の開始値から1ずつ増加する数列(r[0]=a, r[i+1]=r[i]+1)
引数start, stopを設定します。(stepはデフォルトstep=1)
>>> list(range(1,10))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
# 負のインデックス値も使えます
>>> list(range(-5, 5))
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
# start > stopを満たす数列は無いので空(empty)が返ります
>>> list(range(10, 5))
[]
Stepを指定(r[i+1]=r[i]+b)
stepを指定する場合は、第一引数start, 第二引数stopを設定する必要があります。
>>> list(range(0, 10, 2))
[0, 2, 4, 6, 8]
>>> list(range(3, 10, 2))
[3, 5, 7, 9]
# stepを設定する場合は、第一引数startも設定必要。
# 引数が2つの場合は、start, stopと解釈されます。
>>> list(range(10, 2))
[]
引数step = 0とするとValueErrorが返ります。
>>> list(range(1, 5, 0))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: range() arg 3 must not be zero
降順の数列とする場合(r[i+1] = r[i] – b)
引数stepを負の数値に設定すると、降順の数列ができます。
>>> list(range(10, 0, -1))
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
>>> list(range(10, 3, -2))
[10, 8, 6, 4]
>>> list(range(5, -5, -1))
[5, 4, 3, 2, 1, 0, -1, -2, -3, -4]
引数start, stop, stepの数値の大小関係が矛盾した場合は空が返ります
# start < stop、step < 0を満たす数列は無い
>>> list(range(5, 10, -1))
[]
その他特徴
リストやタプルとの比較
公式によると、rangeオブジェクトは設定する数値の範囲に関わらず常に一定のメモリしか使わず効率的とのこと。start, stop, stepのみを保存し、必要に応じて生成するため。
当然といえば当然ですが、一応sys.getsizeof()関数を使って、それぞれのサイズを調べてみました。
>>> import sys
# 0から100,000まで
>>> sys.getsizeof(range(10**5))
48
>>> sys.getsizeof(list(range(10**5)))
900112
確かにrange()は常に一定でリストよりも小さいサイズになっていることが確認できました。
包含判定、要素インデックス検索、スライシング
rangeオブジェクトのままでも、包含判定、要素インデックス検索、スライシングは可能です。
リストやタプルに展開しなくてもよいので、メモリ効率的には良さそうです。
それぞれ具体例を以下に示します。
- 包含判定
>>> 4 in range(10)
True
>>> 99 in range(10)
False
- インデックスでの検索
>>> range(10).index(1)
1
>>> range(10).index(4)
4
>>> range(10)[1]
1
>>> range(10)[4]
4
- スライシング
>> range(10)[1:4]
range(1, 4)
- 内包表記やmap関数でもそのまま使えます
# リスト内包表記
>>> [i for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# map関数での展開
>>> list(map(str, range(5)))
['0', '1', '2', '3', '4']
まとめ
- range()を使うことで、数値シーケンスをrangeオブジェクトで生成することができる
※ start = a, stop = N, step = sとすると、r[i+1] = r[i] + s (r[0] = a, r[i] < N) - リストやタプルにするよりもrangeオブジェクトの方がメモリ効率が良い(大規模データの場合は特に)
- rangeオブジェクトのままでも、包含判定、要素インデックス検索、スライシングは利用可
コメント
[…] この関数はrange(start, stop, step) で生成されるイテラブルからランダムに抽出された要素を返しています。よって、引数(start、stop、step)は、range()と同じです。 […]