网站首页 > 技术文章 正文
在 Python 的使用过程中,大家有没有遇到过这样神奇的现象:代码 1_000_000_000_000_000 in range(1_000_000_000_000_001) 执行起来速度飞快,几乎是瞬间完成。就算你不断增加数字后面的零,计算时间也基本没啥变化,还是快得离谱。但要是你自己写个类似的范围函数,效果就差远了。这到底是咋回事呢?今天咱们就来好好揭秘一番。
错误认知:把 range 当成生成器
很多人觉得 Python 3 里的 range() 函数就跟生成器似的,是动态生成内容的。这么想的话,要判断一个超级大的数(像 1 千万亿)是否在 range() 指定的范围内,就得把这个范围内的所有值都生成一遍,那不得花老长时间嘛。但实际情况却并非如此。
range 其实是序列对象
实际上,range() 返回的是一个序列对象,就像列表一样。你可以通过下面这些操作来验证:
a = range(5)
print(list(a)) # 输出: [0, 1, 2, 3, 4]
print(list(a)) # 再次输出: [0, 1, 2, 3, 4]
如果 range() 是生成器,那第一次迭代完之后它就空了,第二次就啥也输出不了。但这里两次输出一样,说明它不是生成器。而且,我们还能通过代码验证它是序列:
import collections.abc
isinstance(a, collections.abc.Sequence) # 输出: True
作为序列,range() 遵循序列的各种规则,比如可索引、可获取长度、支持成员检测、可反转,还能使用 index 和 count 方法。
range 速度快的原因
range() 对象不会一下子把所有数字都生成出来,它只记录起始值、结束值和步长,等你需要的时候再根据这些信息生成具体的数字。而且,它实现了 __contains__ 方法,这个方法能直接通过数学计算判断一个数是否在范围内,是个近乎常量时间的操作(虽然因为 Python 整数是无界的,数学运算时间会随着数字增大而变长,但在实际中基本不会影响性能)。
咱们看个简单的例子,判断 994 是否在 range(4, 1000, 2) 里,只需要满足两个条件:
- 数字在起始值和结束值之间:4 <= 994 < 1000。
- 数字与起始值的差能被步长整除:(994 - 4) % 2 == 0。
下面是一个简单的 Python 代码示例,模拟 range() 对象的部分功能:
class my_range:
def __init__(self, start, stop=None, step=1):
if stop is None:
start, stop = 0, start
self.start, self.stop, self.step = start, stop, step
if step < 0:
lo, hi, step = stop, start, -step
else:
lo, hi = start, stop
self.length = 0 if lo > hi else ((hi - lo - 1) // step) + 1
def __iter__(self):
current = self.start
if self.step < 0:
while current > self.stop:
yield current
current += self.step
else:
while current < self.stop:
yield current
current += self.step
def __len__(self):
return self.length
def __getitem__(self, i):
if i < 0:
i += self.length
if 0 <= i < self.length:
return self.start + i * self.step
raise IndexError('my_range object index out of range')
def __contains__(self, num):
if self.step < 0:
if not (self.stop < num <= self.start):
return False
else:
if not (self.start <= num < self.stop):
return False
return (num - self.start) % self.step == 0
不过要注意,真正的 range() 还支持很多其他功能,这里只是简单模拟。
特殊情况
要是你传入的不是整数类型,range() 就不会用上面的快速计算方法了,而是会像遍历列表一样,一个一个地比较,速度就会变得很慢。比如:
100000000000000.0 in range(1000000000000001)
这个计算就会花不少时间。
历史原因
在 Python 2 里,xrange() 就没做这个优化。当时有机会把优化加到 Python 2 的 xrange.__contains__ 里,但因为各种原因没这么做。直到 Python 3.2 版本,才对 range.__contains__ 做了优化。
现在大家明白了吧,Python 3 里 range() 的这个特性,其实是开发者们精心设计和优化的结果。在日常使用中,我们可以放心大胆地用 range() 来处理范围判断,既方便又高效。
猜你喜欢
- 2025-07-27 UV 迄今最快、最好的 Python 包管理器
- 2025-07-27 Python“三步”即可爬取,毋庸置疑
- 2025-07-27 第二章:Python 运算符与表达式(python语言中运算符号)
- 2025-07-27 掌握Python比较运算符:核心要点与实战解析
- 2025-07-27 Python开发必会技巧:访问限制(python访问数据)
- 2025-07-27 Python教程(九):While循环与真实示例
- 2025-07-27 Python字符串对齐神技!4种方法让你的输出瞬间专业10倍
- 2025-07-27 Python运算符与表达式(python运算符含义)
- 2025-07-27 一图看懂 Python 2 / Python 3 编码 | CSDN 博文精选
- 2025-07-27 让 Python 代码飙升330倍:从入门到精通的四种性能优化实践
- 08-06中等生如何学好初二数学函数篇
- 08-06C#构造函数
- 08-06初中数学:一次函数学习要点和方法
- 08-06仓颉编程语言基础-数据类型—结构类型
- 08-06C++实现委托机制
- 08-06初中VS高中三角函数:从"固定镜头"到"360°全景",数学视野升级
- 08-06一文讲透PLC中Static和Temp变量的区别
- 08-06类三剑客:一招修改所有对象!类方法与静态方法的核心区别!
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- windowsscripthost (69)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (70)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)