你是否应该总是优先使用xrange()而不是range()?

463

为什么或者为什么不?


36
可以有人简要描述一下这两者之间的区别吗?对于我们这些非Python程序员来说。也许可以这样说:“xrange()做了所有range()的事情,还支持 X、Y 和 Z 功能”。 - Tim Frey
87
range(n)用于创建一个包含0到n-1的整数的列表。如果你使用range(1000000),就会得到一个超过4Mb的列表,这将会成为一个问题。xrange解决了这个问题,它返回一个伪装成列表的对象,但是只计算所要求的索引处的数字,并返回那个数字。 - Brian
4
请访问https://dev59.com/m3VD5IYBdhLWcg3wE3bz。 - James McMahon
4
基本上,range(1000)是一个 list,而 xrange(1000) 是一个像 generator(尽管它确实不是)的对象。同时,xrange更快。你可以使用timeit模块,写两个方法:一个只有 for i in xrange: pass,另一个只有range,然后执行 timeit(method1)timeit(method2),结果会惊人地发现,有时候 xrange 会快近一倍(前提是你不需要一个列表)。例如,对于我来说,i in xrange(1000):passi in range(1000):pass 分别用了大约 13.316725969314575 秒和 21.190124988555908 秒 - 差别还是很大的。 - dylnmc
另一个性能测试显示,xrange(100)range(100) 快了 20%。链接 - Evgeni Sergeev
12个回答

447
为了提高性能,特别是当您迭代大范围时,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()包装时,range不会改变。


5
“range will become an iterator”是什么意思?这不应该是“generator”吗? - Michael Mior
4
生成器指的是一种特定类型的迭代器,而新的 range 本身并不是一个迭代器。 - user2357112
你的第二个要点并没有什么意义。你说你不能多次使用一个对象;当然可以!试试 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 中,所有东西都是对象。我想你的意思是,你可以用 rangexrange 更好地进行基于索引的推导(如果有意义的话)。我认为 range 很少用到。 - dylnmc
虽然我认为如果你想在循环中使用列表并根据某些条件更改某些索引或将东西附加到该列表中,那么 range 可以很方便,但是 xrange 简单地更快且使用的内存更少,因此对于大多数 for 循环应用程序,它似乎是最好的选择。有时候 - 回到提问者的问题 - 很少但确实存在,range 会更好。也许不像我想象的那样少,但我肯定有 95% 的时间都使用 xrange - dylnmc

130

不,它们都各有用处:

在迭代时使用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()


31

另一个区别是Python 2实现的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仍在开发中,但我记得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工作:https://github.com/lodash/lodash/issues/274。切片等操作仍应尽可能地懒惰,如果不是,则只需重新生成。 - Rob Grant

4

以下是关于Go with range的原因:

1) xrange在较新的Python版本中将会被废弃。这使您轻松获得未来的兼容性。

2) range将具备xrange相关的效率优势。


13
不要这样做。xrange()函数将被弃用,但其他很多东西也将被弃用。将Python 2.x代码转换为Python 3.x代码的工具会自动将xrange()转换为range(),但range()会被转换为效率较低的list(range())。 - Thomas Wouters
10
Thomas:实际上它比那聪明一些。当range()在for循环或者列表推导式中不需要一个真正的列表时,它会把它翻译成普通的range()。只有在被赋值给一个变量或者直接使用的情况下,才需要用list()包装。 - Brian

4
书中给出了一个很好的例子:《Python实用教程》,作者是Magnus Lie Hetland。
>>> 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

虽然在大多数情况下,xrangerange更快,但性能差异非常小。下面的小程序比较了迭代rangexrange

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的列表运行此代码。 - Mark
我建议实际修改列表长度:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000) - Mark
哇,这周看起来很不错,谢谢,已经修复了。不过,这并没有太大的区别。 - speedplane

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接