关于python:你是否总是喜欢范围()上的xrange()?

Should you always favor xrange() over range()?

为什么或为什么不呢?


对于性能,特别是在大范围内迭代时,xrange()通常更好。但是,仍有一些情况说明您可能更喜欢range()

    百万千克1

    在python 3中,range()做了xrange()以前做的事情,xrange()不存在。如果您想编写同时在python 2和python 3上运行的代码,就不能使用xrange()

    百万千克1百万千克1

    在某些情况下,range()实际上可以更快——例如,如果多次重复相同的序列。xrange()每次都要重建整数对象,但range()会有真正的整数对象。(但在记忆方面,它总是表现得更差)

    百万千克1百万千克1

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

    百万千克1

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()包装时,范围保持不变。


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

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

1
for x in xrange(1, one_zillion):

而不是:

1
for x in range(1, one_zillion):

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

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


只有当你需要一份实际的清单时,你才应该偏爱range()而不是xrange()。例如,当您想要修改range()返回的列表时,或者当您想要分割它时。对于迭代,甚至仅仅是正常的索引,xrange()将工作得很好(而且通常效率更高)。对于非常小的列表,range()xrange()快一点,但根据您的硬件和其他各种详细信息,盈亏平衡可能是长度1或2的结果,不必担心。最好是xrange()


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

1
2
3
4
5
6
7
8
9
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没有这个问题:

1
2
3
4
5
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)

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

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


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

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

列表使用的额外内存当然不仅仅是浪费,列表还有更多的功能(切片、重复、插入等等)。在文档中可以找到确切的差异。没有原则,使用需要的东西。

python 3.0仍在开发中,但iirc range()与2.x的x range()非常相似,可以使用list(range())生成列表。


我想说的是,获得具有切片和索引功能的xrange对象并没有那么困难。我已经编写了一些代码,它们工作得非常好,并且在计算(迭代)时和xrange一样快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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

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

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

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


请选择范围,原因如下:

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

2)范围将采用与xrange相关的效率。


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

1
2
3
4
5
6
7
8
9
10
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;
for n in range(%d): a += n'
%list_len, number=1000)
  xrtime = timeit.timeit('a=0;
for 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确实更快,但还不足以让人出汗。

1
2
3
4
5
6
7
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,但是除非您使用的是受限制的硬件,否则不要太担心它。


  • range()range(1, 10)返回1到10个数字的列表,并将整个列表保存在内存中。
  • xrange():与range()类似,但不返回列表,而是返回一个按需生成范围内数字的对象。对于循环,这比range()稍快,内存效率更高。像迭代器一样的xrange()对象,并根据需要生成数字(延迟计算)。
1
2
3
4
5
6
7
8
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()所做的相同,python3中不存在术语xrange()。如果多次迭代同一序列,在某些情况下,range()实际上可以更快。xrange()每次都要重建整数对象,但range()会有真正的整数对象。


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

不过,我想你们会想听到的是,首选的选择是xrange。由于python 3中的range是一个迭代器,代码转换工具2to3将正确地将xrange的所有用法转换为range,并对range的用法抛出错误或警告。如果您希望以后能够轻松地转换代码,那么您只需要使用xrange,并且在确定需要列表时使用list(xrange)。我是在今年(2008年)芝加哥的Pycon冲刺赛上学到这一点的。