为什么Python3中没有xrange函数?
最近我开始使用Python3,但发现没有xrange
这个功能让我很困扰。
这里有个简单的例子:
在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()
在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.53888392448
3.215819835662842
这是为什么呢?我想知道,为什么xrange
被去掉了?它是个很棒的工具,特别是对像我这样的初学者来说,大家在某个阶段都是初学者。为什么要把它删掉呢?有没有人能告诉我相关的PEP文档,我找不到。
6 个回答
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)
!
Python3中的range其实就是Python2中的xrange。你不需要再把它放进一个迭代器里。如果你想在Python3中得到一个真正的列表,你需要用list(range(...))
来实现。
如果你想要一个在Python2和Python3中都能用的东西,可以试试这个:
try:
xrange
except NameError:
xrange = range
这里有一些性能测试,使用了 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
的速度稍微慢了一点。这是一个不太重要的情况,没人会特别关注。实际上,几乎没有人会在真实的使用场景中遇到这个性能差异成为代码的瓶颈。