为啥 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),以及可以使用分辨率高数千倍的时钟。 另外,你为什么要测试过滤rangex%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 的代码。这是一个很好的解决方案。 问题是这样,同时使用rangexrange 的代码会表现不同。这样做还不够,还必须确保永远不要假设 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 非常感谢您的澄清。但我将重申原始评论的意图,即 PY3 range() 等同于 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 函数?的主要内容,如果未能解决你的问题,请参考以下文章

range和xrange的区别

NameError:name ‘xrange’ is not defined

xrange与range之间的区别

python 的range()函数怎么使用,为啥单独运行print(range(1,5))输出还是range(1,5),而不是[1,2,3,4]

Python 3-为啥我在这个作业问题中的 try 和 except 函数没有编译? [关闭]

为啥没有在 python3 中检查返回类型? [复制]