为什么Python3中没有xrange函数?


273

最近,我开始使用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,我找不到它。

干杯。


231
rangePython 3.x中的代码xrange来自Python2.x。实际上range是删除了Python2.x 。
Anorov

27
PS,您永远不要与在一起time。除了更易于使用和更难以出错之外,并为您重复测试,还timeit可以处理各种您不会记住的事情,甚至不知道如何处理(例如禁用GC),并且可以使用时钟具有数千倍的更好分辨率。
abarnert

7
另外,为什么还要测试过滤range时间x%4 == 0?为什么不只是测试list(xrange())vs. list(range()),所以尽量减少多余的工作?(例如,您怎么知道3.x的运行x%4速度不是很慢?)为此,您为什么要构建一个庞大的list,其中涉及大量的内存分配(除了速度较慢之外,它的变化也令人难以置信) ?
abarnert

5
请参阅docs.python.org/3.0/whatsnew/3.0.html的 “视图和迭代器而不是列表”部分:“ range()现在的行为类似于xrange(),但它可以处理任意大小的值。后者不复存在。” 因此,range现在返回一个迭代器。iter(range)是多余的。
ToolmakerSteve

9
抱歉,引用更改文档并不能使它变得一目了然。对于任何其他困惑,不想阅读长期接受的答案及其所有注释的人: 无论您在python 2中使用xrange的什么地方,都可以在python 3中使用range。它可以完成xrange过去的工作,即返回一个迭代器。如果您需要列表中的结果,请执行list(range(..))。这相当于python 2的范围。或者说另一种方式:xrange已被重命名为range,因为它是更好的默认值。list(range)如果确实需要列表,则不必同时使用两者。
ToolmakerSteve

Answers:


175

进行一些性能评估,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__在循环中发生的任何事情的1000 万次调用中,对插槽的一次性调用不太可能可见,但有人提出来了。)

但这仅慢了30%。OP如何使速度慢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,即使它的大小是10x的大小,也可以构建不必要的列表问题仍然比范围迭代可能做的任何事情多出10000000x。

那么显式for循环而不是内部的C循环又deque如何呢?

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.3range2.7xrange的源。您甚至可以查看更改历史记录(我相信,该更改已链接到替换文件中任何位置的字符串“ xrange”的最后一个实例的更改)。

那么,为什么它变慢?

好吧,其中之一是,他们添加了许多新功能。另外,他们已经在整个地方(尤其是在迭代过程中)进行了各种具有较小副作用的更改。尽管有时有时会稍微低估不太重要的案例,但仍有大量工作可以极大地优化各种重要案例。将所有这些加起来,令迭代range速度现在变慢会令我感到惊讶。这是最重要的案例之一,没有人会足够关注。没有人会遇到现实中的用例,这种性能差异是他们代码中的热点。


但这仅慢了30%。仍然要慢一些,但是伙伴反应很好,需要考虑一下。但是它并没有回答我的问题:为什么xrange被删除了?以这种方式思考-如果您有一个基于性能的应用程序,该应用程序基于多处理程序而知道一次需要消耗多少队列,那么30%的用户会有所作为吗?你看,你说没关系,但是每次我使用range时,我都会听到巨大的令人烦恼的风扇声音,这意味着cpu处于最坏状态,而xrange却没有。想想你;)
catalesia

9
@catalesia:再次没有将其删除,只是将其重命名了range。的range在3.3对象是的直系后代xrange对象在2.7,而不是的range在2.7功能。就像问何时itertools.imap被撤消以支持map。没有答案,因为没有发生这样的事情。
abarnert

1
@catalesia:性能上的细微变化大概不是直接设计决定使范围变慢的结果,而是整个Python 4年的变化的副作用,这种变化使许多事情变快了,有些事情变慢了(有些事情在x86_64上速度较快,但在x86上速度较慢,在某些用例中速度较快,而在其他一些用例中速度较慢,依此类推)。没人会担心重复range进行任何其他操作所需的时间有两种,两者之间存在30%的差异。
abarnert

1
“没有人担心在不做任何其他事情的情况下迭代范围需要多长时间。无论哪种方式,都存在30%的差异 ”确实。
catalesia

18
@catalesia:是的,完全正确。但是您似乎认为这与它所说的相反。这不是任何人都会关心的用例,因此没有人注意到它的速度降低了30%。所以呢?因此,如果您找到一个在Python 3.3中比在2.7(或2.6)中运行更慢的真实程序,那么人们会很在意的。如果您做不到,他们也不会,您也不应该。
abarnert

141

Python3的范围 Python2的xrange。无需在其周围包装迭代器。要获得Python3中的实际列表,您需要使用list(range(...))

如果您想要适用于Python2和Python3的产品,请尝试以下操作

try:
    xrange
except NameError:
    xrange = range

1
有时您需要在Python 2和3中都可以使用的代码。这是一个很好的解决方案。
格雷格·格洛克纳

3
麻烦的是,与此,代码同时使用range,并xrange会表现不同。这样做还不够,还必须确保永远不要以为range返回列表(就像在python 2中那样)。
LangeHaare

您可以从该项目中使用xrange。有futurize工具可以自动转换您的源代码:python-future.org/…–
guettli

17

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]is range(20, 60, 10)


减慢速度可能是由于查找新版本的xrange次数很多,还是仅进行了一次?
askewchan

迭代器是否确实会提高速度?我以为它只是节省了内存。
askewchan

3
@catalesia我认为这里的要点xrange删除,只是重命名了
askewchan

1
@Blckknght:欢呼,但仍然有类似这样的解释:“设置文字和理解[19] [20] [完成] {x}表示set([x]); {x,y}表示set([[ x,y])。{F(x)表示S中的x,如果P(x)}表示set(F(x)表示S中的x,如果P(x)}。 [range(x)]),而不是set(range(x))。没有空集的文字;请使用set()(或{1}&{2} :-)。没有Frozenset文字;它们也是如此很少需要。
catalesia

3
range就我而言,在3.x中最大的胜利就是恒定时间__contains__。新手曾经写过东西300000 in xrange(1000000),这导致它遍历了整个过程xrange(至少是迭代的前30%),所以我们不得不解释为什么这是一个坏主意,即使它看起来很pythonic。现在,它 pythonic。
abarnert

1

修复python2代码的一种方法是:

import sys

if sys.version_info >= (3, 0):
    def xrange(*args, **kwargs):
        return iter(range(*args, **kwargs))

1
关键是在python3中xrange尚未定义,因此使用xrange的旧代码会中断。
安德鲁·帕特

不,只需定义range = xrange
@John

@ mimi.vx不确定range = xrange在Python3中是否可用,因为未定义xrange。我的评论是指您具有包含xrange调用的旧旧代码,并试图使其在python3下运行的情况。
安德鲁·帕特

1
啊,我的坏事xrange = range.....我换了个陈述
mimi.vx

range 一个迭代器,无论如何,这都是一个糟糕的主意,因为它必须首先解压整个范围,并且失去了使用迭代器进行此类操作的优势。因此,正确的响应不是“ range = xrange”其“ xrange = range”
Shayne

0

Python 2中的xrange是一个生成器,它实现了迭代器,而range只是一个函数。在Python3中,我不知道为什么将其从xrange中删除。


不,范围不是干预者。您不能使用此结构执行next()。欲了解更多信息,您可以在这里查看treyhunner.com/2018/02/python-range-is-not-an-iterator-
米歇尔·费尔南德斯

非常感谢您的澄清。但是,我将重申原始评论的意图,那就是PY3 range()等同于PY2 xrange()。因此在PY3中xrange()是多余的。
史蒂芬·劳赫

-2

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表示x在range(1000000)中,如果x%4]”,number = 1)(正如我们在实际代码中实际使用的那样),python3的运行速度足够快,但在重复循环中,python 2 xrange()在速度上胜过python 3中的range()。


但这仅是语言本身,与xrange / range无关。
mimi.vx
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.