您是否应该始终偏爱xrange()而不是range()?


460

为什么或者为什么不?


36
有人可以为我们的非python人士简要描述这两个之间的区别吗?也许像“ xrange()可以做每一个range()一样,但也支持X,Y和Z”之类的东西
Outlaw程序员

87
range(n)创建一个包含所有整数0..n-1的列表。如果您执行range(1000000),这将是一个问题,因为您最终将获得> 4Mb的列表。xrange通过返回一个伪装成列表的对象来解决此问题,但是只是从所要求的索引中计算出所需的数字,然后返回该数字。
布赖恩


4
基本上,而range(1000)listxrange(1000)是,就像一个物体generator(虽然这当然是之一)。而且,xrange速度更快。您可以import timeit from timeit再进行,只是有一个方法for i in xrange: pass为,另外range,然后做timeit(method1)timeit(method2)和,你瞧,x范围几乎快一倍,有时(也就是当你不需要的列表)。(对我来说,对于i in xrange(1000):passvs 分别i in range(1000):pass花费了13.316725969314575vs 21.190124988555908秒-很多。)
dylnmc 2014年

另一个性能测试提供了xrange(100)高达20%的速度比range(100)
Evgeni Sergeev

Answers:


443

对于性能,尤其是在较大范围内进行迭代时,xrange()通常会更好。但是,在某些情况下,您可能更喜欢range()

  • 在python 3中,做过range()什么xrange()事并且xrange()不存在。如果要编写可在Python 2和Python 3上运行的代码,则不能使用xrange()

  • range()在某些情况下实际上可以更快-例如。如果多次重复相同的序列。 xrange()每次都必须重新构造整数对象,但是range()将拥有真正的整数对象。(但是,在内存方面,它总是会表现得更差)

  • xrange()在需要真实列表的所有情况下都不可用。例如,它不支持切片或任何列表方法。

[编辑]有range()几篇文章提到2to3工具将如何升级。为了记录在案,这里是对一些用法示例运行该工具的输出range()xrange()

RefactoringTool: Skipping implicit fixer: buffer
RefactoringTool: Skipping implicit fixer: idioms
RefactoringTool: Skipping implicit fixer: ws_comma
--- range_test.py (original)
+++ range_test.py (refactored)
@@ -1,7 +1,7 @@

 for x in range(20):
-    a=range(20)
+    a=list(range(20))
     b=list(range(20))
     c=[x for x in range(20)]
     d=(x for x in range(20))
-    e=xrange(20)
+    e=range(20)

如您所见,当在for循环或理解中使用时,或已经用list()包装的地方,范围保持不变。


5
“范围将变成迭代器”是什么意思?这不应该是“发电机”吗?
Michael Mior

4
range不会。生成器是指特定类型的迭代器,而新生成的迭代器并不是迭代器。
user2357112支持Monica 2014年

您的第二项根本没有任何意义。您说的是不能多次使用对象。你当然可以!xr = xrange(1,11)然后在下一行尝试for i in xr: print " ".join(format(i*j,"3d") for j in xr),瞧!您的时间表最多有十个。它的工作原理与r = range(1,11)for i in r: print " ".join(format(i*j,"3d") for j in r)... 一样,一切都是Python2中的对象。我想你的意思说的是,你可以做基于索引的理解(如果是有道理的)更好range,而不是xrange。我认为范围非常方便
-dylnmc 2014年

(没有足够的空间)。虽然,我认为,range如果你想使用一个可以很方便list在一个循环,然后更改基于某些条件或附加的东西到列表中的某些指标,那么range肯定是更好。但是,xrange它速度更快并且使用的内存更少,因此对于大多数for循环应用程序来说,它似乎是最好的。在某些情况下-回到提问者的问题-很少但存在,哪里range会更好。也许不是我想的那么少,但是我肯定有xrange95%的时间在用。
dylnmc 2014年

129

不,它们都有各自的用途:

xrange()在迭代时使用,因为它可以节省内存。说:

for x in xrange(1, one_zillion):

而不是:

for x in range(1, one_zillion):

另一方面,range()如果您确实想要数字列表,请使用。

multiples_of_seven = range(7,100,7)
print "Multiples of seven < 100: ", multiples_of_seven

42

仅当需要实际列表时range()xrange()才应优先考虑。例如,当您想修改所返回的列表range()时,或者希望对其进行切片时。对于迭代,甚至只是普通索引,xrange()都可以正常工作(通常效率更高)。有一点range()要比xrange()很小的列表快一点,但是取决于您的硬件和其他各种细节,收支平衡可能是长度为1或2的结果;不用担心。更喜欢xrange()


30

另一个区别是xrange()不能支持大于C ints的数字,因此,如果要使用python内置的支持大数的数字来获得范围,则必须使用range()。

Python 2.7.3 (default, Jul 13 2012, 22:29:01) 
[GCC 4.7.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> range(123456787676676767676676,123456787676676767676679)
[123456787676676767676676L, 123456787676676767676677L, 123456787676676767676678L]
>>> xrange(123456787676676767676676,123456787676676767676679)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: Python int too large to convert to C long

Python 3没有这个问题:

Python 3.2.3 (default, Jul 14 2012, 01:01:48) 
[GCC 4.7.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> range(123456787676676767676676,123456787676676767676679)
range(123456787676676767676676, 123456787676676767676679)

13

xrange()效率更高,因为它无需生成对象列表,而只是一次生成一个对象。一次只能有一个整数,而不是100个整数及其所有开销和放入它们的列表。生成速度更快,内存使用更好,代码更高效。

除非我特别需要某件事的清单,否则我总是会支持 xrange()


8

range()返回一个列表,xrange()返回一个xrange对象。

xrange()更快,内存使用效率更高。但是收益不是很大。

列表使用的额外内存当然不只是浪费,列表还具有更多功能(切片,重复,插入等)。确切的差异可以在文档中找到。没有硬性规定,请使用所需的。

Python 3.0仍在开发中,但是IIRC range()与2.X的xrange()非常相似,并且list(range())可用于生成列表。


5

我只想说,获取具有切片和索引功能的xrange对象并不难。我编写了一些代码,这些代码相当有效,并且在计数(迭代)时与xrange一样快。

from __future__ import division

def read_xrange(xrange_object):
    # returns the xrange object's start, stop, and step
    start = xrange_object[0]
    if len(xrange_object) > 1:
       step = xrange_object[1] - xrange_object[0]
    else:
        step = 1
    stop = xrange_object[-1] + step
    return start, stop, step

class Xrange(object):
    ''' creates an xrange-like object that supports slicing and indexing.
    ex: a = Xrange(20)
    a.index(10)
    will work

    Also a[:5]
    will return another Xrange object with the specified attributes

    Also allows for the conversion from an existing xrange object
    '''
    def __init__(self, *inputs):
        # allow inputs of xrange objects
        if len(inputs) == 1:
            test, = inputs
            if type(test) == xrange:
                self.xrange = test
                self.start, self.stop, self.step = read_xrange(test)
                return

        # or create one from start, stop, step
        self.start, self.step = 0, None
        if len(inputs) == 1:
            self.stop, = inputs
        elif len(inputs) == 2:
            self.start, self.stop = inputs
        elif len(inputs) == 3:
            self.start, self.stop, self.step = inputs
        else:
            raise ValueError(inputs)

        self.xrange = xrange(self.start, self.stop, self.step)

    def __iter__(self):
        return iter(self.xrange)

    def __getitem__(self, item):
        if type(item) is int:
            if item < 0:
                item += len(self)

            return self.xrange[item]

        if type(item) is slice:
            # get the indexes, and then convert to the number
            start, stop, step = item.start, item.stop, item.step
            start = start if start != None else 0 # convert start = None to start = 0
            if start < 0:
                start += start
            start = self[start]
            if start < 0: raise IndexError(item)
            step = (self.step if self.step != None else 1) * (step if step != None else 1)
            stop = stop if stop is not None else self.xrange[-1]
            if stop < 0:
                stop += stop

            stop = self[stop]
            stop = stop

            if stop > self.stop:
                raise IndexError
            if start < self.start:
                raise IndexError
            return Xrange(start, stop, step)

    def index(self, value):
        error = ValueError('object.index({0}): {0} not in object'.format(value))
        index = (value - self.start)/self.step
        if index % 1 != 0:
            raise error
        index = int(index)


        try:
            self.xrange[index]
        except (IndexError, TypeError):
            raise error
        return index

    def __len__(self):
        return len(self.xrange)

老实说,我认为整个问题有点傻,无论如何xrange都应该做所有这一切……


是的,同意;通过完全不同的技术,检查lodash上的工作以使其变得懒惰:github.com/lodash/lodash/issues/274。切片等仍然应该尽可能地懒惰,如果没有,则只能进行切片。
罗伯·格兰特

4

书中的一个很好的例子:Magnus Lie Hetland的《实用Python》

>>> zip(range(5), xrange(100000000))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

我不建议在前面的示例中使用range而不是xrange -尽管只需要前五个数字,但range会计算所有数字,这可能会花费很多时间。使用xrange,这不是问题,因为它仅计算所需的那些数字。

是的,我读了@Brian的答案:在python 3中,无论如何range()都是生成器,而xrange()不存在。


3

出于以下原因选择范围:

1)xrange将在较新的Python版本中消失。这样可以轻松实现将来的兼容性。

2)range将承担与xrange相关的效率。


13
不要这样 xrange()将会消失,但是其他许多事情也会消失。您将用于将Python 2.x代码转换为Python 3.x代码的工具将自动将xrange()转换为range(),但是range()将转换为效率较低的list(range())。
Thomas Wouters

10
托马斯:实际上比这聪明。在显然不需要真实列表(例如,在for循环或理解中)的情况下,它将将range()转换为仅使用range()。只有将其分配给变量或直接使用的情况下,才必须使用list()进行包装
Brian

2

好的,对于xrange与range的权衡和优势,每个人都有不同的看法。它们大体上是正确的,xrange是一个迭代器,range充实并创建实际列表。在大多数情况下,您不会真正注意到两者之间的差异。(您可以将map与range一起使用,但不能与xrange一起使用,但是会占用更多内存。)

但是,我认为您希望聚会中听到的是,首选是xrange。由于Python 3中的range是一个迭代器,因此代码转换工具2to3可以将xrange的所有用法正确转换为range,并且会针对range的使用抛出错误或警告。如果要确保将来轻松转换代码,则仅使用xrange,并在确定需要列表时使用list(xrange)。我是在今年(2008年)在芝加哥PyCon上进行的CPython冲刺中学到的。


8
这不是真的。像“ for x in range(20)”这样的代码将保留为范围,而像“ x = range(20)”这样的代码将转换为“ x = list(range(20))”-没有错误。此外,如果要编写将在2.6和3.0下运行的代码,则range()是唯一的选择,而无需添加兼容性功能。
布赖恩

2
  • range()range(1, 10)返回1到10个数字的列表,并将整个列表保存在内存中。
  • xrange():和一样range(),但不返回列表,而是返回一个对象,该对象根据需要生成范围内的数字。对于循环,这比速度快一点,range()并且内存效率更高。xrange()像迭代器这样的对象,并按需生成数字(惰性评估)。
In [1]: range(1,10)
Out[1]: [1, 2, 3, 4, 5, 6, 7, 8, 9]

In [2]: xrange(10)
Out[2]: xrange(10)

In [3]: print xrange.__doc__
Out[3]: xrange([start,] stop[, step]) -> xrange object

range()xrange()执行与Python 3中相同的操作,并且在Python 3中不xrange()存在术语。 range()如果多次迭代同一序列,在某些情况下实际上可以更快。xrange()每次都必须重新构造整数对象,但是range()将拥有真正的整数对象。


2

虽然xrangerange大多数情况下要快,但性能差异却很小。下面的小程序比较了对a range和an的迭代xrange

import timeit
# Try various list sizes.
for list_len in [1, 10, 100, 1000, 10000, 100000, 1000000]:
  # Time doing a range and an xrange.
  rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n'%list_len, number=1000)
  xrtime = timeit.timeit('a=0;\nfor n in xrange(%d): a += n'%list_len, number=1000)
  # Print the result
  print "Loop list of len %d: range=%.4f, xrange=%.4f"%(list_len, rtime, xrtime)

下面的结果显示xrange确实更快,但不足以使工作过度。

Loop list of len 1: range=0.0003, xrange=0.0003
Loop list of len 10: range=0.0013, xrange=0.0011
Loop list of len 100: range=0.0068, xrange=0.0034
Loop list of len 1000: range=0.0609, xrange=0.0438
Loop list of len 10000: range=0.5527, xrange=0.5266
Loop list of len 100000: range=10.1666, xrange=7.8481
Loop list of len 1000000: range=168.3425, xrange=155.8719

因此,请务必使用xrange,但是除非您使用的是受限制的硬件,否则不要为它担心太多。


list_len没有被使用,所以你只能在运行此代码长度100的名单
马克

我建议您实际修改列表的长度:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
Mark

哇,这定是一个美好的一周,谢谢,固定。仍然没有太大的区别。
speedplane
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.