为什么在python 3中range(0)==range(2,2,2)为真?

Why is range(0) == range(2, 2, 2) True in Python 3?

为什么用不同值初始化的范围在python 3中互相比较相等?

当我在解释器中执行以下命令时:

1
2
3
4
>>> r1 = range(0)
>>> r2 = range(2, 2, 2)
>>> r1 == r2
True

结果是True。为什么会这样?为什么两个参数值不同的range对象被视为相等?


range对象是特殊的:

python将把range对象作为序列进行比较。这实质上意味着,比较并不评估它们如何表示给定的序列,而是评估它们所表示的内容。

startstopstep参数完全不同的事实在这里没有区别,因为它们在展开时都代表一个空列表:

例如,第一个range对象:

1
list(range(0))  # []

第二个range对象:

1
list(range(2, 2, 2)) # []

两者都代表一个空列表,由于两个空列表比较相等(True,所以表示它们的range对象也一样。

因此,您可以拥有完全不同的外观的range对象;如果它们代表相同的序列,它们将比较相等:

1
range(1, 5, 100) == range(1, 30, 100)

两者都用一个元素[1]表示一个列表,因此这两个元素的比较也相等。

不,range对象非常特殊:

但是,请注意,即使比较没有评估它们如何表示序列,也可以仅使用startsteprange对象的len的值来实现比较结果;这对比较速度有着非常有趣的影响:

1
2
3
4
5
r0 = range(1, 1000000)    
r1 = range(1, 1000000)

l0 = list(r0)    
l1 = list(r1)

范围比较超快:

1
2
3
%timeit r0 == r1
The slowest run took 28.82 times longer than the fastest. This could mean that an intermediate result is being cached
10000000 loops, best of 3: 160 ns per loop

另一方面,清单……

1
2
%timeit l0 == l1
10 loops, best of 3: 27.8 ms per loop

是啊。。

正如@superbiasedman所指出的,这只适用于python 3中的range对象。python 2 range()是一个普通的ol函数,它返回一个列表,而2.xxrange对象没有python 3中range对象所具有的比较功能(不仅是这些功能)。

查看@ajcr的答案,直接从python 3 range对象的源代码中获得报价。这里记录了两个不同范围之间的比较实际需要什么:简单快速的操作。range_equals函数用于EQNE情况下的range_richcompare函数,并分配给PyRange_Type类型的tp_richcompare插槽。

我相信range_equals的实现是非常可读的(因为它非常简单),可以在这里添加:

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
/* r0 and r1 are pointers to rangeobjects */

/* Check if pointers point to same object, example:    
       >>> r1 = r2 = range(0, 10)
       >>> r1 == r2
   obviously returns True. */
if (r0 == r1)
    return 1;

/* Compare the length of the ranges, if they are equal
   the checks continue. If they are not, False is returned. */
cmp_result = PyObject_RichCompareBool(r0->length, r1->length, Py_EQ);
/* Return False or error to the caller
       >>> range(0, 10) == range(0, 10, 2)  
   fails here */
if (cmp_result != 1)
    return cmp_result;

/* See if the range has a lenght (non-empty). If the length is 0
   then due to to previous check, the length of the other range is
   equal to 0. They are equal. */
cmp_result = PyObject_Not(r0->length);
/* Return True or error to the caller.
       >>> range(0) == range(2, 2, 2)  # True
   (True) gets caught here. Lengths are both zero. */
if (cmp_result != 0)
    return cmp_result;

/* Compare the start values for the ranges, if they don't match
   then we'
re not dealing with equal ranges. */
cmp_result = PyObject_RichCompareBool(r0->start, r1->start, Py_EQ);
/* Return False or error to the caller.
   lens are equal, this checks their starting values
       >>> range(0, 10) == range(10, 20)  # False
   Lengths are equal and non-zero, steps don't match.*/
if (cmp_result != 1)
    return cmp_result;

/* Check if the length is equal to 1.
   If start is the same and length is 1, they represent the same sequence:
       >>> range(0, 10, 10) == range(0, 20, 20)  # True */
one = PyLong_FromLong(1);
if (!one)
    return -1;
cmp_result = PyObject_RichCompareBool(r0->length, one, Py_EQ);
Py_DECREF(one);
/* Return True or error to the caller. */
if (cmp_result != 0)
    return cmp_result;

/* Finally, just compare their steps */
return PyObject_RichCompareBool(r0->step, r1->step, Py_EQ);

我也在这里散播了一些我自己的评论;看看@ajcr对python等价物的回答。


直接引用文档(强调我的):

Testing range objects for equality with == and != compares them as
sequences. That is, two range objects are considered equal if they
represent the same sequence of values. (Note that two range objects
that compare equal might have different start, stop and step
attributes, for example range(0) == range(2, 1, 3) or range(0, 3, 2)
== range(0, 4, 2).)

如果将EDOCX1[0]与"相同"列表进行比较,您将得到不平等,如文档中所述:

Objects of different types, except different numeric types, never
compare equal.

例子:

1
2
3
4
5
6
7
8
>>> type(range(1))
<class 'range'>
>>> type([0])
<class 'list'>
>>> [0] == range(1)
False
>>> [0] == list(range(1))
True

注意,这只显式地适用于Python3。在python 2中,range只返回一个列表,range(1) == [0]计算为True


为了在本页的优秀答案中增加一些额外的细节,对两个range对象r0r1进行了大致比较,如下所示:

1
2
3
4
5
6
7
8
9
10
11
if r0 is r1:                 # True if r0 and r1 are same object in memory
    return True
if len(r0) != len(r1):       # False if different number of elements in sequences
    return False
if not len(r0):              # True if r0 has no elements
    return True
if r0.start != r1.start:     # False if r0 and r1 have different start values
    return False
if len(r0) == 1:             # True if r0 has just one element
    return True
return r0.step == r1.step    # if we made it this far, compare step of r0 and r1

使用startstopstep参数可以很容易地计算range对象的长度。例如,在start == stop的情况下,python可以立即知道长度为0。在非常重要的情况下,python可以使用startstopstep值进行简单的算术计算。

因此,在range(0) == range(2, 2, 2)的情况下,python执行以下操作:

  • 发现range(0)range(2, 2, 2)是内存中的不同对象。
  • 计算两个对象的长度;两个长度都为0(因为两个对象中的start == stop),因此需要另一个测试。
  • 发现len(range(0))为0。这意味着len(range(2, 2, 2))也是0(之前的不等式检验失败),因此比较应返回True

  • res = range(0) == range(2, 2, 2)

    哪里:

    1
    range(0)

    指从0000~步骤(此处step~默认值1),不带值列表。

    1
    range(2, 2, 2)

    指步骤等于2的从22的范围,不带值列表。

    所以,这些范围是相等的


    range(0)返回range(0,0)。从0到0的步骤1是未定义的,因为第三个参数不能是0[默认情况下]。使用1无法达到0。没有计数器的动作,因此为0。

    range(2, 2, 2)返回range(2, 2, 2)。从2到2开始,但步骤为2。同样,基本上是0,因为你什么都不算。

    1
    range(0) == range(2,2,2)

    是的,完全一样。