为什么Python3中没有xrange函数?

313 投票
6 回答
430157 浏览
提问于 2025-04-17 16:42

最近我开始使用Python3,但发现没有xrange这个功能让我很困扰。

这里有个简单的例子:

  1. 在Python2中:

    from time import time as t
    def count():
      st = t()
      [x for x in xrange(10000000) if x%4 == 0]
      et = t()
      print et-st
    count()
    
  2. 在Python3中:

    from time import time as t
    
    def xrange(x):
    
        return iter(range(x))
    
    def count():
        st = t()
        [x for x in xrange(10000000) if x%4 == 0]
        et = t()
        print (et-st)
    count()
    

得到的结果分别是:

  1. 1.53888392448
  2. 3.215819835662842

这是为什么呢?我想知道,为什么xrange被去掉了?它是个很棒的工具,特别是对像我这样的初学者来说,大家在某个阶段都是初学者。为什么要把它删掉呢?有没有人能告诉我相关的PEP文档,我找不到。

6 个回答

23

Python 3中的range类型和Python 2中的xrange用法是一样的。我不太明白你为什么会感觉速度变慢,因为你用xrange得到的迭代器和直接用range得到的是一模一样的。

我在我的系统上没有遇到速度变慢的问题。以下是我测试的方法:

在Python 2中,使用xrange

Python 2.7.3 (default, Apr 10 2012, 23:24:47) [MSC v.1500 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import timeit
>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)
18.631936646865853

在Python 3中,使用range稍微快一点:

Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import timeit
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)
17.31399508687869

我最近了解到,Python 3的range类型还有一些其他很酷的功能,比如支持切片:range(10,100,2)[5:25:5]的结果是range(20, 60, 10)

156

Python3中的range其实就是Python2中的xrange。你不需要再把它放进一个迭代器里。如果你想在Python3中得到一个真正的列表,你需要用list(range(...))来实现。

如果你想要一个在Python2和Python3中都能用的东西,可以试试这个:

try:
    xrange
except NameError:
    xrange = range
198

这里有一些性能测试,使用了 timeit,而不是手动用 time 来测量。

首先,测试的是 Apple 2.7.2 64位版本:

In [37]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.05 s per loop

接下来是 python.org 的 3.3.0 64位版本:

In [83]: %timeit collections.deque((x for x in range(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.32 s per loop

In [84]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.31 s per loop

In [85]: %timeit collections.deque((x for x in iter(range(10000000)) if x%4 == 0), maxlen=0) 
1 loops, best of 3: 1.33 s per loop

看来,3.x 版本的 range 确实比 2.x 版本的 xrange 慢一点。而且,提问者的 xrange 函数和这个没有关系。(这也不奇怪,因为在 10000000 次循环中,调用一次 __iter__ 的影响几乎是看不见的,但有人提到过这个可能性。)

不过,它只慢了 30%。那提问者怎么会觉得慢了 2倍呢?如果我用 32位的 Python 重复同样的测试,我得到的结果是 1.58 对比 3.12。所以我猜,这又是一个 3.x 版本为了在 64位上优化性能而牺牲了 32位性能的例子。

但这真的重要吗?再看看这个,还是用 3.3.0 64位:

In [86]: %timeit [x for x in range(10000000) if x%4 == 0]
1 loops, best of 3: 3.65 s per loop

所以,构建 list 的时间比整个迭代的时间还要多出两倍。

至于“消耗的资源比 Python 2.6+ 多得多”,根据我的测试,3.x 的 range 和 2.x 的 xrange 大小是完全一样的——即使它大了 10 倍,构建这个不必要的列表仍然是比任何 range 的迭代问题大约 10000000 倍的问题。

那如果用显式的 for 循环,而不是 deque 内部的 C 循环呢?

In [87]: def consume(x):
   ....:     for i in x:
   ....:         pass
In [88]: %timeit consume(x for x in range(10000000) if x%4 == 0)
1 loops, best of 3: 1.85 s per loop

所以,在 for 语句中浪费的时间几乎和实际迭代 range 的时间一样多。

如果你担心优化 range 对象的迭代,可能你找错地方了。


与此同时,你一直在问为什么 xrange 被移除了,无论多少人告诉你同样的事情,我再重复一遍:它并没有被移除,而是被重命名为 range,而 2.x 的 range 才是被移除的。

这里有一些证据证明 3.3 的 range 对象是 2.x 的 xrange 对象的直接后代(而不是 2.x 的 range 函数):3.3 range 的源代码和 2.7 xrange 的源代码。你甚至可以看到 变更历史(我相信这是替换文件中最后一次出现的字符串 "xrange" 的变更)。

那么,为什么它会更慢呢?

首先,他们增加了很多新功能。其次,他们在各个地方做了各种更改(特别是在迭代中),这些更改有一些小的副作用。而且,他们还做了大量的工作来显著优化各种重要的情况,即使有时候会稍微降低一些不太重要情况的性能。把这些加起来,我并不惊讶现在迭代 range 的速度稍微慢了一点。这是一个不太重要的情况,没人会特别关注。实际上,几乎没有人会在真实的使用场景中遇到这个性能差异成为代码的瓶颈。

撰写回答