你应该总是喜欢 xrange() 而不是 range() 吗?

Posted

技术标签:

【中文标题】你应该总是喜欢 xrange() 而不是 range() 吗?【英文标题】:Should you always favor xrange() over range()? 【发布时间】:2010-09-13 04:45:17 【问题描述】:

为什么或为什么不?

【问题讨论】:

有人可以为我们非 python 的人简单描述一下两者之间的区别吗?也许像“xrange() 可以做所有 range() 的事情,但也支持 X、Y 和 Z” range(n) 创建一个包含所有整数 0..n-1 的列表。如果您执行 range(1000000),这将是一个问题,因为您最终会得到一个 >4Mb 的列表。 xrange 通过返回一个伪装成列表的对象来处理这个问题,但只是从请求的索引中计算出所需的数字,然后返回。 见***.com/questions/94935 基本上,而range(1000)listxrange(1000) 是一个行为类似于generator 的对象(尽管它肯定不是一个)。此外,xrange 更快。你可以import timeit from timeit 然后创建一个只有for i in xrange: pass 和另一个range 的方法,然后执行timeit(method1)timeit(method2) 并且,你瞧,xrange 有时几乎快两倍(也就是说,当你不需要列表)。 (对我来说,i in xrange(1000):passi in range(1000):pass 分别花费了 13.31672596931457521.190124988555908 秒 - 这很多。) Another performance test 使 xrange(100)range(100) 快 20%。 【参考方案1】:

就性能而言,尤其是在大范围迭代时,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 无论如何都不是迭代器。 你的第二个子弹没有任何意义。你是说你不能多次使用一个对象;你当然可以!试试xr = xrange(1,11) 然后在下一行for i in xr: print " ".join(format(i*j,"3d") for j in xr) 瞧!您的时间表最多可达 10 个。它的工作原理与r = range(1,11)for i in r: print " ".join(format(i*j,"3d") for j in r) 相同... Python2 中的一切都是对象。我认为您的意思是,与xrange 相比,range 可以更好地进行基于索引的理解(如果有意义的话)。我认为范围很少能派上用场 (没有足够的空间)虽然,我确实认为如果您想在循环中使用list,然后更改某些基于索引的索引,range 会很方便在某些条件下或将内容附加到该列表中,那么range 肯定会更好。但是,xrange 速度更快,使用的内存更少,因此对于大多数 for 循环应用程序来说,它似乎是最好的。在某些情况下 - 回到提问者的问题 - 很少但存在,range 会更好。也许不像我想的那么少,但我肯定会使用xrange 95% 的时间。【参考方案2】:

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

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

【讨论】:

【参考方案3】:

仅当您需要实际列表时,您才应该使用 range() 而不是 xrange()。例如,当您想要修改 range() 返回的列表时,或者想要对其进行切片时。对于迭代甚至只是普通的索引,xrange() 可以正常工作(通常效率更高)。对于非常小的列表,range()xrange() 快一点,但取决于您的硬件和各种其他细节,收支平衡可能是长度为 1 或 2 的结果;没什么好担心的。首选xrange()

【讨论】:

【参考方案4】:

另一个区别是xrange() 的 Python 2 实现不支持大于 C 整数的数字,所以如果你想使用 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)

【讨论】:

【参考方案5】:

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

除非我特别需要一份清单,否则我总是喜欢xrange()

【讨论】:

【参考方案6】:

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

xrange() 速度更快,内存效率更高。但收益并不是很大。

列表使用的额外内存当然不仅仅是浪费,列表还有更多功能(切片、重复、插入……)。确切的区别可以在documentation 中找到。没有硬性规定,按需使用。

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

【讨论】:

【参考方案7】:

我只想说,获得具有切片和索引功能的 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。切片等仍应尽可能懒惰,如果不这样做,则只有具体化。【参考方案8】:

出于以下原因选择范围:

1) xrange 将在较新的 Python 版本中消失。这为您提供了轻松的未来兼容性。

2) range 将获得与 xrange 相关的效率。

【讨论】:

不要这样做。 xrange() 会消失,但很多其他的东西也会消失。您将用于将 Python 2.x 代码转换为 Python 3.x 代码的工具会自动将 xrange() 转换为 range(),但 range() 将被转换为效率较低的 list(range())。 Thomas:它实际上比这更聪明一点。它会在它显然不需要真正的列表(例如在 for 循环或理解中)的情况下将 range() 转换为纯 range()。只有将它分配给变量或直接使用的情况必须用 list() 包装【参考方案9】:

书中给出的一个很好的例子:Practical Python Magnus Lie Hetland

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

我不建议在前面的示例中使用 range 来代替 xrange——尽管 只需要前五个数字,范围计算所有数字,这可能需要很多时间 的时间。使用 xrange,这不是问题,因为它只计算所需的数字。

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

【讨论】:

【参考方案10】:

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

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

【讨论】:

那不是真的。像“for x in range(20)”这样的代码将保留为 range,而像“x=range(20)”这样的代码将被转换为“x=list(range(20))”——没有错误。此外,如果您想编写可在 2.6 和 3.0 下运行的代码,range() 是您唯一的选择,无需添加兼容性函数。【参考方案11】: 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() 与 Python 3 中的 xrange() 执行相同的操作,并且 Python 3 中不存在术语 xrange()。 如果您多次迭代相同的序列,range() 在某些情况下实际上会更快。 xrange() 每次都必须重构整数对象,但range() 会有真正的整数对象。

【讨论】:

【参考方案12】:

虽然xrange 在大多数情况下都比range 快,但性能差异非常小。下面的小程序比较了对 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 的列表运行此代码。 我建议实际修改列表长度:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000) 哇,这将是美好的一周,谢谢,已修复。不过,差别不大。

以上是关于你应该总是喜欢 xrange() 而不是 range() 吗?的主要内容,如果未能解决你的问题,请参考以下文章

python学习之range()和xrange()

Python range() xrange()

range与xrange

Range和xrange的区别

xrange与range的区别

xrange与range之间的区别