为啥 Python3 中没有 xrange 函数?
Posted
技术标签:
【中文标题】为啥 Python3 中没有 xrange 函数?【英文标题】:Why is there no xrange function in Python3?为什么 Python3 中没有 xrange 函数? 【发布时间】:2013-02-07 11:43:09 【问题描述】:最近我开始使用 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,我找不到。
【问题讨论】:
range
在 Python 3.x 中是 xrange
在 Python 2.x 中。实际上是 Python 2.x 的 range
被删除了。
PS,你永远不应该和time
在一起。 timeit
除了更容易使用、更难出错以及为您重复测试之外,还可以处理您不记得甚至不知道如何处理的各种事情(例如禁用 GC),以及可以使用分辨率高数千倍的时钟。
另外,你为什么要测试过滤range
上x%4 == 0
的时间?为什么不只测试list(xrange())
与list(range())
,所以尽可能少的无关工作? (例如,你怎么知道 3.x 没有更慢地执行x%4
?)对于那个问题,你为什么要构建一个巨大的list
,这涉及到大量的内存分配(除了速度很慢之外) , 也是令人难以置信的变量)?
参见docs.python.org/3.0/whatsnew/3.0.html,“视图和迭代器而不是列表”部分:“range() 现在的行为类似于 xrange() 过去的行为,除了它适用于任意大小的值。后者不再存在。”因此,range 现在返回一个迭代器。 iter(range)
是多余的。
抱歉,引用变更文档并不能让它变得非常明显。对于其他任何感到困惑并且不想阅读长期接受的答案及其所有 cmets 的人:无论您在 python 2 中使用 xrange 的什么地方,在 python 3 中使用 range。它执行 xrange 曾经做的事情,即返回一个迭代器。如果您需要列表中的结果,请执行list(range(..))
。这相当于python 2的范围。 或者换一种说法:已经重命名了范围,因为它是更好的默认值;没有必要同时拥有两者,如果您真的需要列表,请使用list(range)
。.
【参考方案1】:
一些性能测量,使用 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
慢一点。而 OP 的 xrange
函数与它无关。 (不足为奇,因为对 __iter__
插槽的一次性调用在 10000000 次循环中发生的调用中不太可能可见,但有人提出了它的可能性。)
但它只慢了 30%。 OP 是如何变得慢 2 倍的?好吧,如果我用 32 位 Python 重复相同的测试,我会得到 1.58 与 3.12。所以我的猜测是,这是 3.x 以损害 32 位的方式针对 64 位性能进行优化的又一个案例。
但这真的重要吗?检查一下,再次使用 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 倍大,构建不必要的列表仍然比范围迭代可能做的任何事情都要多 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
的实际工作中浪费的时间一样多。
如果您担心优化范围对象的迭代,您可能找错地方了。
同时,你一直问为什么xrange
被删除了,不管人们告诉你多少次同样的事情,但我会再重复一遍:它没有被删除:它被重命名为range
,而2.x range
是被删除的内容。
这里有一些证据表明 3.3 range
对象是 2.x xrange
对象(而不是 2.x range
函数)的直接后代:3.3 range
和 2.7 xrange
的源.你甚至可以看到change history(我相信,链接到文件中任何地方替换字符串“xrange”的最后一个实例的更改)。
那么,为什么它变慢了?
首先,他们添加了许多新功能。另一方面,他们在整个地方(尤其是内部迭代)进行了各种具有轻微副作用的更改。并且已经做了很多工作来显着优化各种重要的案例,即使它有时会略微悲观不太重要的案例。把这一切加起来,我并不感到惊讶的是,尽可能快地迭代 range
现在有点慢。这是没有人会足够关注的次要案例之一。没有人可能会遇到这样的实际用例,其中这种性能差异是他们代码中的热点。
【讨论】:
但它只慢了 30%。仍然较慢,但反应很好,值得考虑。但它没有回答我的问题:为什么 xrange 被删除了?这样想——如果你有一个基于多处理的依赖于性能的应用程序,知道你需要一次消耗多少队列,30% 会有所不同吗?你看,你说没关系,但每次我使用范围时,我都会听到巨大的令人痛苦的风扇声音,这意味着 cpu 处于最差状态,而 xrange 没有这样做。想想你;) @catalesia:再一次,它没有被删除,它只是重命名为range
。 3.3 中的 range
对象是 2.7 中 xrange
对象的直接后代,而不是 2.7 中 range
函数的直接后代。这就像在 itertools.imap
被删除以支持 map
时询问。没有答案,因为没有发生这样的事情。
@catalesia:细微的性能变化可能不是直接设计决定使范围变慢的结果,而是整个 Python 4 年变化的副作用,它使许多事情变得更快,一些事情有点慢(有些事情在 x86_64 上更快但在 x86 上更慢,或者在某些用例中更快但在其他用例中更慢,等等)。没有人可能担心在不执行其他任何操作的情况下迭代 range
所需的时间会有 30% 的差异。
"没有人可能担心迭代一个范围所需的时间会有 30% 的差异而其他什么都不做。" 没错。
@catalesia:是的,完全正确。但你似乎认为这与它所说的相反。这不是任何人都会关心的用例,所以没有人注意到它慢了 30%。所以呢?如果您能因此找到一个在 Python 3.3 中比在 2.7(或 2.6)中运行得更慢的实际程序,人们会关心的。如果你不能,他们不会,你也不应该。【参考方案2】:
Python3 的范围是 Python2 的xrange。没有必要在它周围包裹一个迭代器。要在 Python3 中获取实际列表,需要使用 list(range(...))
如果你想要一些适用于 Python2 和 Python3 的东西,试试这个
try:
xrange
except NameError:
xrange = range
【讨论】:
有时您需要同时适用于 Python 2 和 3 的代码。这是一个很好的解决方案。 问题是这样,同时使用range
和xrange
的代码会表现不同。这样做还不够,还必须确保永远不要假设 range
正在返回一个列表(就像在 python 2 中那样)。
您可以使用此项目中的 xrange。有futurize
工具可以自动转换你的源代码:python-future.org/…【参考方案3】:
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)
!
【讨论】:
减速可能来自于多次查找新的xrange
,还是只进行了一次?
迭代器真的能提高速度吗?我以为它只是节省了内存。
@catalesia 我认为这里的重点是xrange
没有被删除,只是重命名。
@Blckknght:干杯,但它仍然很烂,有这样的解释:“设置文字和理解 [19] [20] [完成] x 表示 set([x]); x, y 表示 set([x, y])。F(x) for x in S if P(x) 表示 set(F(x) for x in S if P(x))。注意。 range(x) 表示 set([range(x)]),NOT set(range(x))。空集没有文字;使用 set()(或 1&2 :-)。没有 freezeset 文字;很少需要它们。"
就我而言,3.x range
的最大胜利是恒定时间 __contains__
。新手过去常常写300000 in xrange(1000000)
,这导致它迭代整个xrange
(或至少前30%),所以我们不得不解释为什么这是一个坏主意,即使它看起来很pythonic。现在,它是 pythonic。【参考方案4】:
修复 python2 代码的一种方法是:
import sys
if sys.version_info >= (3, 0):
def xrange(*args, **kwargs):
return iter(range(*args, **kwargs))
【讨论】:
重点是在python3中xrange没有定义,所以使用xrange的遗留代码会中断。 不,只需定义range = xrange
就像@John La Roy 的评论中一样
@mimi.vx 不确定 range=xrange 是否适用于 Python3,因为未定义 xrange。我的评论是指您拥有包含 xrange 调用的旧遗留代码并且您试图让它在 python3 下运行的情况。
啊,我的错..xrange = range
...我切换语句
range IS 一个迭代器,无论如何这将是一个糟糕的主意,即使它不是,因为它必须先解包整个范围并且失去了对这类事情使用迭代器。所以正确的反应不是“range=xrange”而是“xrange=range”【参考方案5】:
Python 2 中的 xrange 是一个生成器并实现了迭代器,而 range 只是一个函数。 在 Python3 中,我不知道为什么会从 xrange 中删除。
【讨论】:
不,范围不是一个交互器。你不能用这个结构做 next() 。欲了解更多信息,您可以在这里查看treyhunner.com/2018/02/python-range-is-not-an-iterator 非常感谢您的澄清。但我将重申原始评论的意图,即 PY3range()
等同于 PY2 xrange()
。因此在 PY3 xrange()
中是多余的。【参考方案6】:
comp:~$python Python 2.7.6(默认,2015 年 6 月 22 日,17:58:13) [GCC 4.8.2] 在 linux2 上
>>> import timeit
>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)
5.656799077987671
>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)
5.579368829727173
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)
21.54827117919922
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)
22.014557123184204
timeit number=1 参数:
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=1)
0.2245171070098877
>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=1)
0.10750913619995117
comp:~$ python3 Python 3.4.3(默认,2015 年 10 月 14 日,20:28:29) [GCC 4.8.4] 在 Linux 上
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)
9.113872020003328
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)
9.07014398300089
timeit number=1,2,3,4 参数以线性方式快速工作:
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=1)
0.09329321900440846
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=2)
0.18501482300052885
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=3)
0.2703447980020428
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=4)
0.36209142999723554
因此,如果我们测量 1 个运行循环周期,例如 timeit.timeit("[x for x in range(1000000) if x%4]",number=1) (正如我们在实际代码中实际使用的那样),python3 可以工作足够快,但在重复循环中,python 2 xrange() 在速度上胜过 python 3 中的 range()。
【讨论】:
但这是语言本身...与 xrange/range 无关。以上是关于为啥 Python3 中没有 xrange 函数?的主要内容,如果未能解决你的问题,请参考以下文章
NameError:name ‘xrange’ is not defined
python 的range()函数怎么使用,为啥单独运行print(range(1,5))输出还是range(1,5),而不是[1,2,3,4]