Python / range() / 連続する整数や等間隔の整数を作る

Pythonのrange()は、一定間隔の整数列を表す不変シーケンスを作ります。開始値は含み、終端の境界stopは含みません。stepの符号で増加・減少を指定します。全要素を格納するリストや、反復で消費されるジェネレーターとは異なります。

start・stop・stepの基本規則

rangeの公式説明にはrange(stop)、range(start, stop)、range(start, stop, step)があります。省略時のstartは0、stepは1です。引数は整数か整数インデックス規約に対応するオブジェクトで、通常の実数や数値文字列を自動変換しません。

Python 3で各例の全体をUTF-8のrange_example.pyに保存し、python range_example.py、環境に応じてpython3で実行します。内容を置き換えて独立に試せます。小さな範囲はlist()で全要素を確認します。

print(list(range(4)))
print(list(range(1, 6)))
print(list(range(1, 10, 2)))

期待される出力:

[0, 1, 2, 3]
[1, 2, 3, 4, 5]
[1, 3, 5, 7, 9]

range(4)は0~3、range(1, 6)は1~5です。増加・間隔1で5を含めるならstopを6にします。間隔2では最後がstopの直前の整数になるとは限らず、間隔と境界の両条件を満たす値までです。

負のstepと空の範囲

正のstepではstop未満、負のstepではstopより大きい値を含みます。減少には負の間隔が必要です。方向が境界に合わなければ、エラーではなく空の範囲になります。

print(list(range(10, -5, -3)))
print(list(range(5, 0, -1)))
print(list(range(5, 0)))
print(list(range(0, 5, -1)))
print(list(range(3, 3)))
[10, 7, 4, 1, -2]
[5, 4, 3, 2, 1]
[]
[]
[]

range(10, -5, -3)は10、7、4、1、-2です。次の-5はstop自身なので除外します。5から1へ下げるならrange(5, 0, -1)です。開始と終端が同じ場合も空で、その範囲のループ本体は一度も実行されません。

リストではなくても添字と反復が使える

rangeの表示は全要素ではなく範囲の表現です。境界と間隔から値を計算し、長さ・添字・スライス・所属判定に対応します。不変なので添字への代入で要素を変更できません。

numbers = range(2, 10, 2)
print(numbers)
print(len(numbers))
print(numbers[0], numbers[-1])
print(list(numbers[1:3]))
print(6 in numbers, 7 in numbers)
print(list(numbers))
print(list(numbers))
range(2, 10, 2)
4
2 8
[4, 6]
True False
[2, 4, 6, 8]
[2, 4, 6, 8]

同じrangeを2回リストにしても同じ要素が得られます。range自体は消尽するジェネレーターではありません。iter(numbers)で別に作るイテレーターは消費されるため区別します。スライスもrangeを返し、例では確認のためリスト化しています。

stepが0・実数・文字列の場合のエラー

間隔0は進行を定義できずValueErrorです。3.0は整数に見えてもfloat、"3"は文字列なので、どちらもTypeErrorになります。

for arguments in ((1, 5, 0), (3.0,), ("3",)):
    try:
        print(range(*arguments))
    except (ValueError, TypeError) as error:
        print(type(error).__name__)
ValueError
TypeError
TypeError

整数文字列を受け付けるなら別途検証してint()で変換します。実数を無条件に切り捨てると意図しない範囲になるので、入力方針を先に決めます。この例外処理は3つの不正入力の比較であり、失敗を無視して成功扱いにする方法ではありません。

反復回数と項目の番号に合わせて選ぶ

rangeを使う反復は回数や整数の段階が必要なときに適します。項目だけ必要ならリストを直接反復し、番号も必要ならenumerate()で添字計算を減らせます。

for number in range(1, 4):
    print("Step", number)

names = ["Ada", "Bo"]
for number, name in enumerate(names, start=1):
    print(number, name)
Step 1
Step 2
Step 3
1 Ada
2 Bo

最初は1~3の3段階です。次のstart=1は番号の開始を変えるだけで、先頭項目を飛ばしません。実際の添字や位置の計算が必要ならrange(len(items))も使えます。

巨大な範囲と実際の処理資源を区別する

整数リスト全体を保存せず大きな境界を表せますが、全要素の処理時間は省略できません。CPythonではlen()が表せる長さの上限により、rangeを作れても長さの計算でOverflowErrorになる場合があります。

import sys

numbers = range(sys.maxsize + 1)
print(numbers[0])
print(numbers[-1] == sys.maxsize)
try:
    print(len(numbers))
except OverflowError:
    print("OverflowError")
0
True
OverflowError

例は先頭・末尾と長さのエラーだけを調べ、全体を走査しません。sys.maxsizeは環境によって異なります。巨大な範囲を安易にlist()にせず、必要な区間だけ反復するか順次処理します。