range 对象
range 是 Python 内置的不可变序列类型,用于生成等差整数序列。它并非真正存储所有数字的列表,而是一个"按需计算"的惰性对象,因此在表示巨大数字范围时具有极高的内存效率。
range 的类型与构造
range 有三种构造形式:
range(5) # 0, 1, 2, 3, 4,默认从0开始,步长1
range(1, 6) # 1, 2, 3, 4, 5,指定起始和结束(结束不包含)
range(0, 10, 2) # 0, 2, 4, 6, 8,指定步长
参数规则与切片一致:start 包含,stop 不包含,步长默认为 1。步长可以是负数,实现递减序列:
list(range(5, 0, -1)) # [5, 4, 3, 2, 1]
list(range(10, -1, -2)) # [10, 8, 6, 4, 2, 0]
当步长为正时,start 必须小于 stop,否则结果为空;步长为负时,start 必须大于 stop:
list(range(5, 1)) # [],start > stop 且步长为正
list(range(1, 5, -1)) # [],start < stop 且步长为负
步长不能为 0,否则会抛出 ValueError:
range(1, 5, 0) # ValueError: range() arg 3 must not be zero
惰性求值与不可变性
range 对象在创建时不生成任何数字,只记录 start、stop、step 三个参数。当你迭代它或请求特定索引时,才实时计算出对应的值:
r = range(1000000)
print(r) # range(0, 1000000),不占用大量内存
print(len(r)) # 1000000,长度可立即确定
print(r[999999]) # 999999,按需计算
这种惰性机制使 range 的内存占用与范围大小无关。一个 range(0, 10**9) 对象仅占几十字节,而等价的列表需要约 8GB 内存。
range 是不可变序列,支持索引访问和切片,但不支持元素赋值:
r = range(10)
r[3] # 3,支持索引
r[2:5] # range(2, 5),支持切片,返回新的 range 对象
r[3] = 99 # TypeError: 'range' object does not support item assignment
切片操作返回新的 range 对象,而非列表。若需查看具体值,必须显式转换:
r = range(0, 20, 2)
r[3:6] # range(6, 12, 2)
list(r[3:6]) # [6, 8, 10]
与列表的转换
range 本身不是列表,但可以通过 list() 轻松转换:
list(range(5)) # [0, 1, 2, 3, 4]
list(range(3, 8)) # [3, 4, 5, 6, 7]
list(range(0, 10, 3)) # [0, 3, 6, 9]
在需要多次随机访问或修改元素的场景下,转换为列表是必要的;但在仅需顺序迭代的循环中,直接使用 range 即可,无需转换:
# 不需要 list(),直接迭代更省内存
for i in range(1000000):
pass
# 错误示范:浪费内存
for i in list(range(1000000)):
pass
range 支持 in 成员检测,但实现方式与列表不同。对于 range,成员检测是 O(1) 的算术判断;对于列表,则是 O(n) 的线性扫描:
r = range(0, 1000000, 2)
999998 in r # True,瞬间完成
999999 in r # False,瞬间完成
判断逻辑是:检查数值是否在 [start, stop) 区间内,且与 start 的差能被 step 整除。
内存效率对比
通过 sys.getsizeof() 可以直观感受 range 的内存优势:
import sys
r = range(1000000)
lst = list(r)
print(sys.getsizeof(r)) # 约 48 字节
print(sys.getsizeof(lst)) # 约 8000048 字节(800多万字节)
range 的内存占用恒定为几十字节,而列表需要为每个元素分配指针和对象开销。在表示大规模整数序列时,range 是无可替代的选择。
等效性判断
两个 range 对象即使参数不同,只要生成的序列完全相同,就被视为相等:
range(0, 5) == range(5) # True
range(0, 6, 2) == range(0, 5, 2) # True,都生成 [0, 2, 4]
但它们的参数元组 (start, stop, step) 可能不同,因此 repr 输出也不同。这种"值相等但构造不同"的特性,是 range 作为数学抽象而非物理存储的体现。