优秀的编程知识分享平台

网站首页 > 技术文章 正文

Python 3 中 “1000000000000000 in range(1000000000000001)” 为何快

nanyue 2025-05-03 15:45:42 技术文章 16 ℃

在 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() 遵循序列的各种规则,比如可索引、可获取长度、支持成员检测、可反转,还能使用 indexcount 方法。

range 速度快的原因

range() 对象不会一下子把所有数字都生成出来,它只记录起始值、结束值和步长,等你需要的时候再根据这些信息生成具体的数字。而且,它实现了 __contains__ 方法,这个方法能直接通过数学计算判断一个数是否在范围内,是个近乎常量时间的操作(虽然因为 Python 整数是无界的,数学运算时间会随着数字增大而变长,但在实际中基本不会影响性能)。

咱们看个简单的例子,判断 994 是否在 range(4, 1000, 2) 里,只需要满足两个条件:

  1. 数字在起始值和结束值之间:4 <= 994 < 1000
  2. 数字与起始值的差能被步长整除:(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() 来处理范围判断,既方便又高效。

Tags:

最近发表
标签列表