网站首页 > 技术文章 正文
在 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-05-03 如何在 Python 中创建一个不可变的字典 - Adam Johnson
- 2025-05-03 Python中while循环详解(python写while循环)
- 2025-05-03 详细介绍一下Python中的BeautifulSoup库的使用?
- 2025-05-03 python魔法方法:__repr__与__str__
- 2025-05-03 Python 编译加速技术(python编译速度)
- 2025-05-03 Python 运算符大揭秘(python运算符代码)
- 2025-05-03 Python | range()详解(python 中 range)
- 2025-05-03 Python6大基础运算符,看完这篇之后会让你有一个彻底认识
- 2025-05-03 「Python编程规范」语句分隔符号(python用什么作为分隔符)
- 2025-05-03 Python中的虚拟环境和包管理(python虚拟环境conda)
- 最近发表
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)