欢迎光临
我们一直在努力

Python 的 range 为什么能装下一亿个数还不卡?拆开惰性序列与懒计算

一、48 字节里装了什么

range 对象在 C 层面就是一个固定结构体,只存三个整数:start、stop、step。一亿个数并不存在于内存里,它们是一个"公式":第 i 个元素 = start + i * step你要第 5 个数,我现算给你;要第 9999 个,也是现算。存储被计算替代了——这就是惰性序列的本质。对比一下代价(CPython 3.13 实测):| 写法 | 内存 ||—|—|| list(range(10**7)) | 80,000,056 字节 ≈ 80MB |先做个实验:
pyth| `range(10**7)` | 48 字节 | 一千万个数,差距是 166 万倍。## 二、遍历不但不慢,反而更快直觉上"现算"应该比"现成"慢,实测正好相反:pythonsum(list(range(107))) # 429mssum(range(107)) # 226ms,快 47%原因藏在内存访问里:list 存的是 `PyObject*` 指针数组,每取一个元素要先解引用指针、再拿对象;range 每步直接做整数加法,连指针都不用碰。**少一次解引用,一千万次循环就省出一半时间。**onimport syspr## 三、更离谱的:一亿个数里找元素只要 14 微秒 python>>> 9999999 in range(108)True # 耗时 14μs>>> 9999999 in list(range(107))True # 耗时 340ms对 list 来说,`in` 是从头到尾逐个比对的 O(n) 扫描;而 range 知道自己的 start、stop、step,**用一道算术题就能判断成员**:int(sys.get(x – start) 能被 step 整除,且 0 <= (x – start)/step < len
O(1) 对 O(n),这次是 24000 倍的差距。## 四、range 是"懒",但它不是生成器很多人把 range 和生成器混为一谈,其实它比生成器"实在"得多:pythonr = range(100)len® # 100,可以随时问长度r[50] # 50,可以随机访问sum® # 遍历完了还能再来一遍sum® # 重复迭代,结果不变sizeof(range(10 生成器是一次性的(耗尽即空),range 却可以反复遍历、切片、取 len。而且切片返回的还是 range:pythonrange(100)[10:90:2] # range(10, 90, 2) —— 依然 48 字节```代价是它不可变:r[0] = 99 直接抛 TypeError。三个数字段的等差数列,本来就无处可改。## 五、什么时候该用 range- 只需要数数:for i in range(n) 永远优先于 list(range(n));0))) – 大区间判断、等差遍历:range 的 O(1) 成员判断是隐藏福利;

  • 需要反复遍历的等差序列:用 range,别转成 list,更别生成 list 之后又想省内存。一句话总结:range 把"一亿个数"压缩成三个数加一个公式。 写 Python 时养成一个反射——先问自己"这份数据必须真的存在吗",很多时候内存和速度是白捡的。(实测环境:CPython 3.13,Windows;数字均可复现) # 48print(sys.getsizeof(range(108))) # 48````range(1)和range(100000000)` 占的内存一模一样,都是 48 字节**。一亿个整数,每个对象按 28 字节算,真摆出来得 2.8GB——而 range 只用了 48 字节,连一张纸都占不满。这不是魔法,是一种设计思想:惰性(lazy)——数据不用的时候,就不真的存在。
赞(0)
未经允许不得转载:171主机测评 » Python 的 range 为什么能装下一亿个数还不卡?拆开惰性序列与懒计算
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址