Python range函数生成什么序列及基本用法
详细解析 Python 中 range 函数生成的不可变整数等差序列类型与惰性计算内存机制,系统讲解单参数、双参数与三参数(含负步长)的基本语法、边界规则与实战技巧。
range() 函数生成的并不是一个直接在内存中装满数字的实体列表(list),而是一个专门表示不可变整数等差序列的 range 类型对象。它采用惰性计算(按需生成)机制,无论其声明的数值范围是一百还是十亿,在内存中都始终只占用极小且恒定的常数空间(O(1) 空间复杂度)。本文将直接剖析 range 对象的本质机制,并系统拆解其单参数、双参数与三参数(含负步长)的具体语法规则及典型应用场景。
一、range 函数究竟生成了什么序列?
初学 Python 时,很多读者因经常在 for i in range(5): 中看到连续数字,容易误认为 range() 返回的是一个包含 [0, 1, 2, 3, 4] 的列表。但如果在 Python 交互环境中直接打印它的返回值,会看到截然不同的结果:
r = range(5)
print(r)
print(type(r))
# 示例输出:
range(0, 5)
<class 'range'>
从输出可以看出,range(5) 返回的是一个独立的 range 类实例。在 Python 3 中,它属于标准的不可变内置序列类型(Immutable Sequence),与 list、tuple 平级。
1. 惰性计算与 O(1) 内存机制
range 对象的核心优势在于惰性求值(Lazy Evaluation)。无论你定义的序列范围多么庞大,对象内部仅仅记录三个整型属性:start(起始值)、stop(终止界限)和 step(步长)。它绝不会一次性在内存中铺开所有整数,而是在迭代器请求下一个值、或者用户通过索引访问某一项时,根据数学公式动态计算对应位置的数值。
我们可以通过 sys.getsizeof() 来直观对比 range 与 list 在内存消耗上的本质差异:
import sys
r = range(0, 1000000)
l = list(r)
print("range 内存占用(字节):", sys.getsizeof(r))
print("list 内存占用(字节):", sys.getsizeof(l))
# 示例输出:
range 内存占用(字节): 48
list 内存占用(字节): 8000056
包含 100 万个数字的 list 需要在物理内存中开辟连续空间并存储全部对象指针,占用约 8MB 内存;而同样覆盖 0 到 100 万的 range 对象仅占用 48 字节。即便把范围扩大到十亿甚至百亿,range 的内存占用依然是 48 字节,绝不会引起内存溢出。
2. range 具备哪些序列特性?
虽然 range 不预存所有数字,但它完全实现了 Python 的序列抽象基类,具备大部分只读序列操作:
- 获取长度:可以使用
len(r)直接获取序列包含的元素总数,复杂度为 O(1)。 - 下标索引访问:支持正向与负向索引,例如
r[0]取得首项,r[-1]取得末项,由内部公式直接寻址。 - 切片操作:对 range 切片(如
r[2:8:2])得到的结果依然是一个新的range对象,同样零开销。 - 常数级成员检测:执行
x in r时,Python 3 并不会从头逐个比对,而是使用算术公式(x - start) % step == 0结合范围边界在 O(1) 时间内完成判断。 - 不可变性:不支持原地修改或项赋值。执行
r[0] = 10将引发TypeError: 'range' object does not support item assignment。
二、range 函数的三种传参形态与区间推导
range() 函数支持三种不同的调用签名,由参数个数决定起始点与步长增量的取值方式:
range(stop)
range(start, stop)
range(start, stop, step)
无论是哪一种调用方式,都严格遵循左闭右开区间 [start, stop) 的规则:包含起始值 start,但在达到或超过终止值 stop 之前停止,即绝对不包含 stop 本身。
形态一:单参数 range(stop)
当仅传入一个参数时,该参数被视作 stop。此时起始值 start 自动默认为 0,步长 step 自动默认为 1。生成的序列区间为 [0, stop),共包含 stop 个整数。
for num in range(5):
print(num, end=" ")
# 示例输出:
0 1 2 3 4
stop <= 0,例如 range(0) 或 range(-3),函数不会报错,而是生成一个长度为 0 的空序列。
形态二:双参数 range(start, stop)
当传入两个参数时,第一个参数指定序列的起始数值 start,第二个参数指定终止界限 stop,步长 step 依然默认为 1。生成的区间为 [start, stop)。
for num in range(3, 8):
print(num, end=" ")
# 示例输出:
3 4 5 6 7
如果传入的起始值大于或等于终止值(例如 range(5, 5) 或 range(8, 3)),由于默认步长向正方向移动,无法到达终止条件,将生成一个空序列。
形态三:三参数 range(start, stop, step)
当传入三个参数时,第三个参数显式指定每次递进的步长 step。序列中的第 k 项满足 x_k = start + k * step,直至数值达到或跨越 stop 为止。
# 生成 1 到 10 之间的所有奇数
for num in range(1, 10, 2):
print(num, end=" ")
# 示例输出:
1 3 5 7 9
step 必须为非零整数。如果传入 step=0,Python 会直接抛出 ValueError: range() arg 3 must not be zero。
三、正向递增与逆向递减:步长与边界陷阱
当需要递减遍历或倒数计数时,必须使用三参数形式并传入负数步长(step < 0)。在负步长下,序列的运行方向从左向右变为从右向左,边界规则也随之转变。
1. 负步长的运行条件与边界
使用负步长时,必须满足 start > stop,否则生成的序列为空。更关键的是,“不包含 stop” 的规则在递减时依然完全生效:
# 需求:从 5 倒数到 1
for i in range(5, 0, -1):
print(i, end=" ")
# 示例输出:
5 4 3 2 1
2. 倒数至 0 的经典边界陷阱
很多开发者容易犯的一个错误是:想要倒数到 0 时,习惯性把 stop 写成了 0。
正如上面的代码所示,range(5, 0, -1) 的终止值是 0,按照排除规则,序列到达 1 就已经停下,数字 0 根本不会被打印。如果你需要让倒数包含 0,stop 必须设定为 -1:
# 正确包含 0 的写法:stop 设置为 -1
for i in range(5, -1, -1):
print(i, end=" ")
# 示例输出:
5 4 3 2 1 0
| 调用形态与参数 | 步长方向 | 有效区间产生条件 | 最终生成的数字序列 |
|---|---|---|---|
range(4) |
+1 (默认) | stop > 0 | 0, 1, 2, 3 |
range(2, 6) |
+1 (默认) | start < stop | 2, 3, 4, 5 |
range(0, 10, 3) |
+3 (正步长) | start < stop | 0, 3, 6, 9 |
range(6, 2, -1) |
-1 (负步长) | start > stop | 6, 5, 4, 3 |
range(5, 1) |
+1 (默认) | start > stop(无解) | 空序列(len=0) |
range(2, 6, -1) |
-1 (负步长) | start < stop(无解) | 空序列(len=0) |
四、实际开发中的常见场景与写法辨析
1. 固定次数循环(执行 N 次)
当仅仅需要重复执行某段逻辑指定次数、而不需要关心当前数字时,通常配合匿名变量 _ 使用 range(n):
def retry_connect(attempts=3):
for i in range(attempts):
print(f"正在进行第 {i + 1} 次重试...")
# 执行网络连接逻辑
2. 遍历序列:range(len(...)) 还是 enumerate()?
在许多传统语言背景的开发者代码中,经常能看到利用 range(len(items)) 通过下标索引遍历列表的写法:
fruits = ["苹果", "香蕉", "橙子"]
# 传统写法(不推荐):
for i in range(len(fruits)):
print(i, fruits[i])
# Pythonic 推荐写法:
for i, fruit in enumerate(fruits):
print(i, fruit)
辨析建议:如果遍历过程中既需要索引又需要元素本身,推荐优先使用内置的 enumerate() 函数。它在语义上更直观,执行效率也比每次通过下标回查列表更加高效。只有在需要同时跳跃访问、反向定位或需要同步修改原列表特定切片时,才考虑显式使用 range 生成索引。
3. 需要实体数据容器时的显式转换
如果后续逻辑必须使用列表方法(如 .append()、.pop() 或原地排序 .sort()),或者需要将数字序列序列化为 JSON,可以通过构造函数直接将其转换为 list 或 tuple:
nums_list = list(range(1, 6))
nums_tuple = tuple(range(1, 6))
print(nums_list)
print(nums_tuple)
# 示例输出:
[1, 2, 3, 4, 5]
(1, 2, 3, 4, 5)
五、核心要点总结与避坑清单
为了在实际编码中避免低级错误,可以将 range 的核心特性与边界约束整理为如下自查清单:
- 参数只认整数:
range()的所有参数必须是整型(int),传入浮点数(如range(0.5, 3.5))会直接引发TypeError: 'float' object cannot be interpreted as an integer。如果需要生成浮点数等差数列,建议使用标准库生成器或第三方科学计算库(如numpy.arange/numpy.linspace)。 - 牢记左闭右开:终止界限
stop永远不被包含在序列内。凡是需要生成包含上限 N 的序列,stop都必须设定为N + 1。 - 负步长别忘终点多减 1:逆向递减数到 0 时,
stop必须是-1;数到 1 时,stop是0。 - 避免过早实体化:在大数据量或大规模迭代循环中,直接遍历
range对象即可,切勿随手加一层list(range(...)),以免无谓消耗系统内存。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









