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

为什么或者为什么不呢?

213847 次浏览

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

除非我特别需要一个列表,否则我总是喜欢xrange()

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

选择范围有以下几个原因:

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

2) range将具有与xrange相关的效率。

对于性能,特别是当你在一个大范围内迭代时,xrange()通常更好。然而,仍然有一些情况下你可能更喜欢range():

  • 在python 3中,range()xrange()以前做的事情,而xrange()不存在。如果你想编写可以在Python 2和Python 3上运行的代码,你不能使用xrange()

  • 在某些情况下,range()实际上可以更快。如果在同一序列上迭代多次。xrange()每次都必须重构整数对象,但range()将具有实整数对象。(然而,在内存方面,它总是表现得更差)

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

[编辑]有几篇文章提到了如何用2to3工具升级range()。为了记录,下面是在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保持不变。

Range()返回列表,xrange()返回xrange对象。

Xrange()更快一点,内存效率更高一点。但是收益不是很大。

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

Python 3.0仍在开发中,但IIRC range()将非常类似于xrange()的2。X和list(range())可以用来生成列表。

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

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

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

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

我只是想说,获得一个具有切片和索引功能的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应该做所有这些…

另一个区别是Python 2实现的xrange()不支持大于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)

书中给出了一个很好的例子:实际Python By 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()不存在。

  • 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()将有真实的整数对象

虽然在大多数情况下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,但除非你在一个受限的硬件上,否则不要太担心它。