← 返回题目列表

Python 3 的 range 是什么?为什么 range(10**18) 不会撑爆内存?

中等 第 19 / 21 题 更新于 2026/07/31
Pythonrange惰性序列

简化版

Python 3 的 range 不是列表,而是一个「惰性的不可变序列对象」——它不会真的把所有数字存进内存,只保存 startstopstep 三个值,需要某个数时按公式 start + i * step 现算,所以 range(10**18) 只占几十字节、不会撑爆内存。关键点:① 惰性(lazy)——range(n) 不生成 n 个数,占用的内存和 n 无关(永远是常数级);② 是「序列」不是「迭代器」——支持索引 r[i](O(1) 现算)、切片 r[2:5](返回新 range)、len(r)in 判断(x in range(...) 是 O(1) 数学判断、不是逐个遍历),而且能重复遍历(迭代器只能遍历一次,range 可以反复用);③ 不可变、可比较range(3) == range(0,3) 为 True,按值比较)。Python 2 的坑:Py2 的 range() 返回列表(真占内存)、xrange() 才是惰性;Py3 统一成惰性的 rangexrange 取消)。核心记忆:range 是惰性不可变序列(只存 start/stop/step、按需现算),内存与长度无关,支持索引/切片/O(1) 成员判断、可重复遍历;不是列表也不是一次性迭代器。

详细版

range 的特性

特性说明
惰性只存 start/stop/step,内存 O(1)(与长度无关)
是序列支持索引 r[i]、切片 r[i:j]、len、reversed
可重复遍历不像迭代器只遍历一次,range 可反复迭代
O(1) 成员判断x in range(...) 是数学计算,不是逐个查
不可变、可比较range(3) == range(0,3,1) 为 True
# 惰性:巨大 range 也不占内存
r = range(10**18)
import sys
print(sys.getsizeof(r))     # 48(字节,常数级,和长度无关!)
print(sys.getsizeof(range(10)))   # 48(一样大)

# 是序列:支持索引、切片、len
r = range(0, 100, 10)       # 0,10,20,...,90
print(r[3])                 # 30(O(1) 现算:0 + 3*10)
print(r[-1])                # 90
print(r[2:5])               # range(20, 50, 10)(切片返回新 range)
print(len(r))               # 10

# O(1) 成员判断(数学计算,不遍历)
print(50 in range(0, 10**9, 2))    # True(瞬间,不逐个查)
print(51 in range(0, 10**9, 2))    # False

# 可重复遍历(迭代器只能一次)
r = range(3)
print(list(r))              # [0, 1, 2]
print(list(r))              # [0, 1, 2](还能再遍历!)

# 可比较(按值,不看 start/step 表示形式)
print(range(0, 3) == range(3))          # True
print(range(0, 4, 2) == range(0, 5, 2)) # True(都是 0,2)

# 反向
print(list(reversed(range(3))))         # [2, 1, 0]

# 转列表才真正生成(占内存)
lst = list(range(10**6))    # 这才真的占内存

⚠️ 核心认知:Python 3 的 range 是一个「假装成序列的公式」——它不存数据,只存 start/stop/step,任何操作(取第 i 个、判断 x 在不在、算长度)都是「用公式现场算」,所以内存永远是常数、range(10**18)range(10) 一样大。这解释了它的两个高频考点:① for i in range(n) 高效——不会先造一个 n 元素的列表再遍历,而是边算边给,省内存又快;② x in range(...) 是 O(1)——因为它用数学判断「x 是否等于某个 start + i*step 且在范围内」,不是像 list 的 in 那样逐个比较(O(n))。还要分清三个概念:range惰性序列(可索引、可切片、可重复遍历);迭代器(如 iter(range(3))、生成器)是一次性的(遍历完就空);list实打实存数据的。Python 2 里 range() 返回列表、xrange() 才惰性,Python 3 把 range 统一成惰性、废弃了 xrange——这是 Py2→Py3 常见的认知调整。

完整版教学

一、range 是惰性序列,不是列表

先纠正最常见的误解:

误解:range(n) 生成一个包含 0~n-1 的列表
真相:range(n) 是一个"惰性序列对象",不存数据

range 对象内部只保存三个整数:
  start(起始,默认 0)
  stop(结束,不含)
  step(步长,默认 1)
  → 就这三个值,不管 range 多大

生成第 i 个元素:按公式现算
  range 的第 i 个元素 = start + i * step
  → 需要哪个才算哪个,不预先生成所有

内存证明:
  sys.getsizeof(range(10))     → 48 字节
  sys.getsizeof(range(10**18)) → 48 字节(一样!)
  → 内存和 range 长度无关(常数级)

对比 list:
  list(range(10**6)) → 真的存 100 万个整数(占几十 MB)
  range(10**6)       → 只存 start/stop/step(48 字节)

为什么设计成惰性:
  for i in range(n) 是最常见的循环
  若每次都造一个 n 元素列表 → 浪费内存(尤其大 n)
  惰性:边算边给,内存 O(1)、速度快

所以 range 是惰性序列(只存 start/stop/step、按公式现算),不是列表,内存 O(1)

误解range(n) 生成一个包含 0~n-1 的列表。真相range(n) 是一个「惰性序列对象」、不存数据。range 内部只保存三个整数start(起始默认 0)、stop(结束不含)、step(步长默认 1)——就这三个值、不管 range 多大。生成第 i 个元素按公式现算start + i * step(需要哪个才算哪个、不预先生成所有)。内存证明sys.getsizeof(range(10))range(10**18) 都是 48 字节(内存和长度无关、常数级)。对比 list:list(range(10**6)) 真存 100 万整数(几十 MB)、range(10**6) 只存三个值(48 字节)。为什么设计成惰性for i in range(n) 是最常见循环、若每次造 n 元素列表浪费内存、惰性边算边给内存 O(1)。理解「range 是惰性序列(只存 start/stop/step、第 i 个=start+i*step 现算)不是列表;内存 O(1)与长度无关(48 字节);list(range())才真占内存;惰性为省 for 循环内存」,就纠正了误解。

二、range 是序列——支持索引/切片/len

理解 range 作为「序列」的能力:

range 是"序列(sequence)",支持序列操作:

① 索引 r[i](O(1),现算):
   r = range(0, 100, 10)
   r[3]   → 30(算 0 + 3*10)
   r[-1]  → 90(支持负索引)
   → 不遍历,直接公式算,O(1)

② 切片 r[i:j](返回新 range,不是 list):
   r[2:5]  → range(20, 50, 10)
   r[::2]  → range(0, 100, 20)(隔一个)
   r[::-1] → range(90, -10, -10)(反向)
   → 切片仍是惰性 range(不生成数据)

③ len(r)(O(1)):
   len(range(0, 100, 10))  → 10
   → 按 (stop-start)/step 算,不数

④ reversed(r):
   reversed(range(3))  → range 反向迭代器 → 2,1,0

⑤ 迭代:
   for i in range(n)   # 边算边给

⑥ 其他:r.count(x)、r.index(x)、r.start/stop/step

不支持(因为不可变):
   r[0] = 5   # ✗ 不能改(range 不可变)
   r.append() # ✗ 无此方法

序列 vs 迭代器(关键):
   range 是序列 → 可索引、可切片、可重复遍历、有 len
   迭代器(生成器)→ 只能顺序遍历一次、无索引、无 len

所以 range 是序列:支持索引 r[i](O(1)现算)、切片(返回新 range)、len、reversed

range 是「序列(sequence)」,支持序列操作① 索引 r[i](O(1)、现算)range(0,100,10)[3] → 30、支持负索引、不遍历直接公式算);② 切片 r[i:j](返回新 range 不是 list)r[2:5]range(20,50,10)r[::-1] 反向、仍是惰性 range);len(r)(O(1))(按 (stop-start)/step 算不数);reversed(r)⑤ 迭代(边算边给);⑥ 其他countindexstart/stop/step)。不支持(因为不可变)r[0]=5append序列 vs 迭代器(关键):range 是序列(可索引、可切片、可重复遍历、有 len)、迭代器(生成器)只能顺序遍历一次(无索引、无 len)。理解「range 是序列:索引 ri、切片(返回新 range 仍惰性)、len(O(1))、reversed、count/index;不可变不能改;序列(可索引/切片/重复遍历)vs 迭代器(一次性)」,就掌握了 range 的序列能力。

三、O(1) 成员判断

理解 x in range(...) 为什么是 O(1):

x in range(...) 是 O(1)(数学判断,不遍历):

普通序列的 in(list/tuple):O(n)
  x in [0,1,...,n-1]  # 逐个比较,最坏 n 次

range 的 in:O(1)(对整数)
  x in range(start, stop, step)
  → 用数学判断:
    ① start <= x < stop(在范围内)?
    ② (x - start) % step == 0(能被步长整除对齐)?
  两个条件都满足 → x 在 range 里
  → 不遍历,直接算,O(1)

例子:
  50 in range(0, 10**9, 2)
  → 0<=50<10**9 ✓ 且 (50-0)%2==0 ✓ → True(瞬间)
  51 in range(0, 10**9, 2)
  → (51-0)%2 != 0 → False(瞬间)
  → 即使 range 有 5 亿个数,判断也是瞬间

注意:只对"整数"是 O(1)
  非整数 x(如 float、其他类型)in range:
  → 退化成逐个比较(O(n)),但通常不会这么用

实用价值:
  快速判断"某整数是否落在某等差序列里"
  if age in range(18, 65):    # 18<=age<65 的快速判断
  if idx in range(len(arr)):  # 索引合法性(虽然直接比较也行)

对比 list:
  50 in list(range(10**9))  # 先造 5 亿的列表(撑爆内存) 再 O(n) 查
  50 in range(10**9)        # O(1),不占内存
  → range 完胜

所以 x in range 对整数是 O(1)(数学判断:在范围内+能被 step 整除),不遍历

x in range(...) 是 O(1)(数学判断、不遍历)。普通序列的 in(list/tuple)是 O(n)(逐个比较)。range 的 in 是 O(1)(对整数):用数学判断——start <= x < stop(在范围内)② (x - start) % step == 0(能被步长整除对齐),两个条件都满足则 x 在 range 里(不遍历、直接算)。例子:50 in range(0, 10**9, 2)0<=50<10**950%2==0 → True(瞬间,即使 5 亿个数)。注意只对「整数」是 O(1)(非整数 in range 退化成逐个比较 O(n)、但通常不这么用)。实用价值:快速判断某整数是否落在某等差序列里(if age in range(18, 65))。对比 list:50 in list(range(10**9)) 先造 5 亿列表(撑爆内存)再 O(n) 查、50 in range(10**9) O(1) 不占内存。理解「x in range 对整数 O(1)(数学判断:start<=x<stop 且(x-start)%step==0),不遍历;list 的 in 是 O(n);实用快速判断整数在等差序列;非整数退化 O(n)」,就理解了 O(1) 成员判断。

四、序列 vs 迭代器 vs 列表

理解 range 和迭代器、列表的区别:

三个容易混的概念:

range(惰性序列):
  ✓ 惰性(不存数据、按需算)
  ✓ 可索引 r[i]、可切片、有 len
  ✓ 可重复遍历(迭代多少次都行)
  ✓ 不可变
  → "假装成序列的公式"

迭代器(iter、生成器):
  ✓ 惰性(按需产出)
  ✗ 不可索引、无 len
  ✗ 只能遍历一次(耗尽就空)
  → "一次性的数据流"
  it = iter(range(3))
  list(it)  → [0,1,2]
  list(it)  → [](已耗尽!)

list(实体列表):
  ✗ 不惰性(真存所有数据、占内存)
  ✓ 可索引、可切片、有 len、可改
  ✓ 可重复遍历
  → "实打实的数据容器"

关键对比(可重复遍历 + 惰性):
  重复遍历:range ✓、list ✓、迭代器 ✗
  惰性省内存:range ✓、迭代器 ✓、list ✗
  → range 独占"惰性 + 可重复遍历 + 可索引"

range 可重复遍历的原理:
  range 每次 for 都新建一个迭代器(不改变 range 本身)
  → 每次遍历都从头(因为 range 是"公式",随时能重算)
  迭代器没有这个能力(它自己就是"进度",用完即止)

所以 range 惰性+可重复遍历+可索引;迭代器惰性但一次性;list 占内存但可重复

三个容易混的概念range(惰性序列)——惰性(不存数据按需算)+ 可索引/切片/len + 可重复遍历 + 不可变(「假装成序列的公式」);迭代器(iter、生成器)——惰性但不可索引/无 len/只能遍历一次(耗尽就空,it = iter(range(3)); list(it) 第一次 [0,1,2]、第二次 []);list(实体列表)——不惰性(真存数据占内存)+ 可索引/切片/len/可改 + 可重复遍历。关键对比:重复遍历(range ✓、list ✓、迭代器 ✗)、惰性省内存(range ✓、迭代器 ✓、list ✗)——range 独占「惰性 + 可重复遍历 + 可索引」range 可重复遍历的原理:每次 for 都新建一个迭代器(不改变 range 本身、每次从头、因为 range 是公式随时能重算),迭代器没有这能力(它自己就是进度、用完即止)。理解「range 惰性+可重复遍历+可索引(公式);迭代器惰性但一次性(耗尽即空);list 占内存但可重复;range 独占惰性+可重复+可索引;range 每次 for 新建迭代器所以能重复遍历」,就区分了三个概念。

五、Python 2 vs 3 与常见用法

理解版本差异和实用场景:

Python 2 vs Python 3(重要迁移点):
  Python 2:
    range(n)  → 返回列表(真占内存!range(10**8) 会很慢/爆内存)
    xrange(n) → 惰性(类似 Py3 的 range)
  Python 3:
    range(n)  → 惰性(等价 Py2 的 xrange)
    xrange    → 取消(不存在了)
  → Py2 迁 Py3:xrange 改 range;依赖 range 返回列表的代码要 list(range())

常见用法:
  ① 循环 N 次:
     for _ in range(n): ...
  ② 索引遍历(但优先 enumerate):
     for i in range(len(seq)): ...   # 尽量用 enumerate
  ③ 生成等差数列:
     range(0, 100, 5)   # 0,5,10,...,95
     range(10, 0, -1)   # 10,9,...,1(递减)
  ④ 快速造列表:
     list(range(1, 11))  # [1,2,...,10]
  ⑤ 整数范围判断:
     if x in range(18, 65): ...   # O(1)

注意:
  range 只支持整数(不能 range(0, 1, 0.1))
  → 浮点等差用 numpy.arange 或 [i*0.1 for i in range(10)]

省内存的意义:
  sum(range(10**8))   # 惰性,不占内存,逐个累加
  sum(list(range(10**8)))  # 先造 1 亿列表(几百 MB)→ 浪费

所以 Py3 的 range 惰性(Py2 是列表、xrange 惰性);只支持整数、浮点用 numpy.arange

Python 2 vs Python 3(重要迁移点)Py2 的 range(n) 返回列表(真占内存、range(10**8) 爆内存)、xrange(n) 惰性Py3 的 range(n) 惰性(等价 Py2 的 xrange)、xrange 取消——Py2 迁 Py3:xrange 改 range、依赖 range 返回列表的代码要 list(range())常见用法:① 循环 N 次 for _ in range(n)、② 索引遍历(优先 enumerate)、③ 生成等差数列 range(0,100,5)range(10,0,-1) 递减、④ 快速造列表 list(range(1,11))、⑤ 整数范围判断 if x in range(18,65)(O(1))。注意:range 只支持整数(不能 range(0,1,0.1))、浮点等差用 numpy.arange[i*0.1 for i in range(10)]省内存的意义sum(range(10**8)) 惰性不占内存、sum(list(range(10**8))) 先造 1 亿列表浪费。理解「Py3 range 惰性(Py2 是列表、xrange 惰性,迁移要改);用法循环 N 次/等差数列/递减 range(10,0,-1)/快速造列表;只支持整数、浮点用 numpy.arange;sum(range())省内存」,就掌握了版本差异与用法。

六、总结与实践

总结 range 的特性:

核心特性:
  range 是"惰性、不可变序列"
  只存 start/stop/step,第 i 个元素 = start + i*step(现算)
  内存 O(1)(与长度无关)

能力:
  可索引 r[i](O(1))、可切片(返回新 range)、len(O(1))
  可重复遍历(不像迭代器一次性)
  x in range 对整数 O(1)(数学判断)
  不可变、可比较、可 reversed

三者区别:
  range:惰性 + 可索引 + 可重复遍历(公式)
  迭代器:惰性 + 一次性(数据流)
  list:占内存 + 可索引 + 可重复(实体)

实用:
  循环 for i in range(n)
  等差数列 range(start, stop, step)
  整数范围判断 x in range(a, b)(O(1))
  巨大范围也不占内存(range(10**18))

坑/注意:
  Py2 range 返回列表、xrange 惰性(Py3 统一惰性)
  只支持整数(浮点用 numpy.arange)
  需要真列表时 list(range(...))

核心总结:
  range 惰性不可变序列(只存 3 个值、按需算)
  内存 O(1)、可索引/切片/重复遍历、in 是 O(1)
  不是列表也不是一次性迭代器

所以 range 惰性序列(存 3 值现算),内存 O(1),可索引/切片/重复遍历,in 是 O(1)

核心特性:range 是「惰性、不可变序列」(只存 start/stop/step、第 i 个 = start + i*step 现算、内存 O(1) 与长度无关)。能力:可索引 r[i](O(1))、可切片(返回新 range)、len(O(1))、可重复遍历、x in range 对整数 O(1)、不可变可比较可 reversed。三者区别:range(惰性+可索引+可重复遍历)、迭代器(惰性+一次性)、list(占内存+可索引+可重复)。实用:循环、等差数列、整数范围判断、巨大范围不占内存。坑/注意:Py2 range 返回列表(Py3 统一惰性)、只支持整数(浮点用 numpy.arange)、需要真列表用 list(range())。理解「range 惰性不可变序列(存 3 值按需算);内存 O(1)、可索引/切片/重复遍历、in 是 O(1);不是列表也不是一次性迭代器;Py2 是列表、只支持整数」,就掌握了总结与实践。

记忆钩子:「Python 3 的 range 不是列表而是『惰性的不可变序列对象』——它只保存 start/stop/step 三个整数,不真的存所有数字,第 i 个元素按公式 start+i*step 现算,所以★range(1018)只占 48 字节(和 range(10)一样大)、内存 O(1)与长度无关,不会撑爆内存;range 是『序列』不是『迭代器』:①支持索引 ri、切片 r[2:5](返回新 range 仍惰性)、len(O(1))、reversed②★能重复遍历(迭代器只能遍历一次耗尽即空,range 每次 for 都新建迭代器、可反复用)③★x in range 对整数是 O(1)(用数学判断:start<=x<stop 且(x-start)%step==0,不是像 list 那样逐个比较 O(n))④不可变、可比较(range(3)==range(0,3)为 True);三者区分:range 惰性+可索引+可重复遍历(公式)、迭代器惰性但一次性(数据流)、list 占内存但可索引可重复(实体);★Py2 的坑:Py2 的 range()返回列表(真占内存)、xrange()才惰性,Py3 统一成惰性 range、废弃 xrange;range 只支持整数(浮点等差用 numpy.arange),需要真列表用 list(range())」**。

七、常见误区与追问

  • 误区:range(n) 会生成一个包含 n 个数字的列表。 不会——Python 3 的 range 是惰性序列对象,内部只保存 start、stop、step 三个整数,不存储任何数字,需要第 i 个元素时才按公式 start + i * step 现算;所以 range(10**18) 只占约 48 字节(和 range(10) 一样大)、内存和长度无关;只有 list(range(n)) 才会真正生成 n 个数字、占用大量内存。
  • 误区:range 和生成器一样只能遍历一次。 不是——range 是「序列」不是「一次性迭代器」,可以反复遍历任意多次(每次 for 循环都会基于 range 新建一个迭代器、从头开始,因为 range 本质是「公式」、随时能重算);生成器和 iter(range(3)) 这样的迭代器才是一次性的(遍历完就耗尽、再遍历得到空);range 还支持索引、切片、len,这些生成器都不支持。
  • 误区:x in range(…) 和 x in list 一样是 O(n) 遍历。 对整数 x 是 O(1)——range 的成员判断用数学计算:检查 start <= x < stop(x - start) % step == 0,两个条件都满足就在范围内,不需要逐个遍历;所以 50 in range(0, 10**9, 2) 瞬间返回、即使范围有 5 亿个数;而 x in list(...) 是逐个比较、O(n);注意如果 x 不是整数(如 float),range 的 in 会退化成逐个比较。
  • 误区:range 支持浮点步长,比如 range(0, 1, 0.1)。 不支持——range 只接受整数参数(start、stop、step 都必须是整数),range(0, 1, 0.1) 会抛 TypeError;需要浮点等差数列用 numpy.arange(0, 1, 0.1)、或用列表推导式 [i * 0.1 for i in range(10)]、或 numpy.linspace(指定端点和个数)。
  • 追问:为什么 range(1018) 不会撑爆内存,而 list(range(1018)) 会? 因为 range 是惰性的——range(10**18) 只是创建了一个对象、内部保存 start=0、stop=10**18、step=1 这三个整数(外加对象头),总共约 48 字节,不管范围多大都是这个常数级大小,需要某个数时才用公式 start + i*step 现算;而 list(range(10**18)) 会强制「物化」——真的去生成 10^18 个整数对象并存进列表,这需要天文数字的内存(每个 int 对象几十字节 × 10^18),根本不可能分配、会直接内存溢出;所以能用惰性 range 就别转成 list,比如 sum(range(10**8)) 是逐个累加、几乎不占内存,sum(list(range(10**8))) 则要先造一个几百 MB 的列表。
  • 追问:range、迭代器、list 三者有什么区别,各适合什么场景? range 是「惰性的不可变序列」:不存数据(内存 O(1))、但支持索引/切片/len、可重复遍历、成员判断对整数 O(1)——适合「表示一个整数范围、做循环计数、范围判断」;迭代器(含生成器)是「惰性的一次性数据流」:按需产出、省内存、但不能索引/无 len/只能遍历一次——适合「处理大数据流、一次性消费、惰性计算管道」;list 是「实体容器」:真存所有数据(占内存)、可索引/切片/修改、可重复遍历——适合「需要随机访问、修改、多次使用具体数据」的场景;简言之:范围/计数用 range,流式处理用生成器,需要存取具体数据用 list。
  • 追问:Python 2 和 Python 3 的 range 有什么不同,迁移时要注意什么? Python 2 里 range(n) 返回一个真正的列表(会立即生成所有元素、占内存,range(10**8) 会很慢甚至内存溢出),而 xrange(n) 才是惰性的(类似 Python 3 的 range);Python 3 把 range 统一改成了惰性版本(等价于 Py2 的 xrange)、并取消了 xrange(不再存在);迁移注意:① Py2 代码里的 xrange 要改成 range;② 如果 Py2 代码依赖「range 返回列表」的行为(比如 range(3) + [4] 拼接、或对结果做 .append、索引赋值),在 Py3 里要显式 list(range(3));③ 好处是迁移后大范围循环自动变省内存了(不用再手动用 xrange)。

八、加强记忆

Python 3 的 range 不是列表而是「惰性的不可变序列对象」——它只保存 start/stop/step 三个整数、不真的存所有数字,第 i 个元素按公式 start + i * step 现算,所以 range(10**18) 只占 48 字节(和 range(10) 一样大)、内存 O(1) 与长度无关、不会撑爆内存range 是「序列」不是「迭代器」① 支持索引 r[i](O(1) 现算)、切片 r[2:5](返回新 range、仍惰性)、len(O(1))、reversed;② 能重复遍历(迭代器只能遍历一次、耗尽即空,range 每次 for 都新建迭代器、可反复用);x in range 对整数是 O(1)(用数学判断 start <= x < stop(x-start) % step == 0,不是像 list 那样逐个比较 O(n));④ 不可变、可比较range(3) == range(0,3) 为 True)。三者区分:range(惰性+可索引+可重复遍历、公式)、迭代器(惰性但一次性、数据流)、list(占内存但可索引可重复、实体)。Py2 的坑:Py2 的 range() 返回列表(真占内存)、xrange() 才惰性,Py3 统一成惰性 range、废弃 xrange。range 只支持整数(浮点等差用 numpy.arange),需要真列表用 list(range())。一句话「range 是惰性不可变序列(只存 start/stop/step、按公式现算),内存 O(1)、range(1018)才 48 字节;可索引/切片/len/可重复遍历、x in range 对整数 O(1);不是列表也不是一次性迭代器;Py2 的 range 返回列表、xrange 才惰性**」。