为什么范围(0)==范围(2,2,2)在Python 3中为True?

为什么在Python 3中,使用不同的值进行初始化的范围比较相等?

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

>>> r1 = range(0) >>> r2 = range(2, 2, 2) >>> r1 == r2 True 

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

range对象是特殊的:

Python会将range对象作为序列进行比较。 实质上意味着比较不会评估它们如何表示给定的顺序,而是它们代表的是什么

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

例如,第一个range对象:

 list(range(0)) # [] 

和第二个range对象:

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

两者都表示一个空列表,并且由于两个空列表比较相等( True ),所以表示它们的range对象也是如此。

因此,您可以拥有完全不同的望远镜物体; 如果他们代表相同的顺序,他们会比较相等:

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

两者都代表一个单一的元素[1]所以这两个也将相等。


不, range对象真的很特别:

但要注意的是,即使比较没有评估它们如何表示一个序列,比较的结果也可以 使用start的值, steprange对象的值来实现 ; 这与比较的速度有非常有趣的含义:

 r0 = range(1, 1000000) r1 = range(1, 1000000) l0 = list(r0) l1 = list(r1) 

范围比较超快:

 %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 

另一方面,名单..

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

是啊..


正如@SuperBiasedMan所指出的,这只适用于Python 3中的范围对象。Python 2 range()是一个普通的ol函数,返回一个列表,而2.x xrange对象没有比较容量( 而且不仅仅是这些..range对象在Python 3中有。

直接从Python 3 range对象的源代码中查看@ ajcr的答案 。 它在那里logging了两个不同范围实际上需要的比较:简单的快速操作。 range_richcompare函数用于EQNE情况下的range_richcompare函数 ,并分配给PyRange_Typetypestp_richcompare插槽

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

 /* 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范围对象与==和!=相等,将它们作为序列进行比较。 也就是说,如果两个范围对象表示相同的值序列,则它们被认为是相等的 。 (注意,比较相等的两个范围对象可能具有不同的开始,停止和步骤属性,例如范围(0)==范围(2,1,3)或范围(0,3,2)==范围(0, 4,2)。)

如果您将range与“相同”列表进行比较,您将得到不平等,正如文档中所述 :

除了不同的数字types,不同types的对象永远不会相等。

例:

 >>> type(range(1)) <class 'range'> >>> type([0]) <class 'list'> >>> [0] == range(1) False >>> [0] == list(range(1)) True 

请注意,这显然只适用于Python 3.在Python 2中, range只是返回一个列表, range(1) == [0]评估为True

为了给这个页面上的优秀答案增加一些额外的细节,两个range对象r0r1 大致如下比较:

 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 

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

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

  1. 看到range(0)range(2, 2, 2)是内存中的不同对象。
  2. 计算两个对象的长度; 两个长度都是0(因为在两个对象中start == stop ),所以需要另一个testing。
  3. 看到len(range(0))为0.这意味着len(range(2, 2, 2))也是0(前面的不等式testing失败),所以比较应该返回True

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

哪里:

 range(0) 

表示从000步的范围(这里的step等于默认值1 ),列表中没有值。

 range(2, 2, 2) 

意思是从22的范围,步长等于2 ,列出没有值。

所以,这些范围真的是平等的

range(0)返回range(0,0) 。 您可以从0开始,直到第1步,这是未定义的,因为第三个参数不能是0 [默认情况下]。 你不能达到0与1.没有计数器的行动,因此0。

range(2, 2, 2)返回range(2, 2, 2) 。 你从2开始到2,但是2的步骤。再次,基本上是0,因为你不计算任何东西。

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

真实完全一样。