为啥 range(0) == range(2, 2, 2) 在 Python 3 中为真?

Posted

技术标签:

【中文标题】为啥 range(0) == range(2, 2, 2) 在 Python 3 中为真?【英文标题】:Why is range(0) == range(2, 2, 2) True in Python 3?为什么 range(0) == range(2, 2, 2) 在 Python 3 中为真? 【发布时间】:2016-05-02 10:40:34 【问题描述】:

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

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

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

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

【问题讨论】:

一个简短但恕我直言的充分答案也将是“因为两个范围相等”。注意is== 之间的区别。 【参考方案1】:

range 对象很特殊:

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

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

例如第一个range对象:

list(range(0))  # []

和第二个range 对象:

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

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

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

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

两者都表示具有单个元素 [1] 的列表,因此这两个也将比较相等。


不,range 对象真的很特别:

但请注意,即使比较不评估如何它们表示一个序列,比较的结果可以实现使用单独 strong> startstep 的值以及 range 对象的 len;这对比较的速度有非常有趣的影响:

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对象不具备 Python 3 中 range 对象所具有的比较功能 (and not only these..)。

查看 @ajcr's answer 以获取直接来自 Python 3 range 对象的源代码的引号。那里记录了两个不同范围之间的比较实际上需要什么:简单快速的操作。 range_equals 函数在 range_richcompare function 中用于 EQNE 情况,并分配给 tp_richcompare slot for PyRange_Type types

我相信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);

我也在这里散布了一些我自己的cmets;查看 @ajcr's answer 以获得 Python 等效项。

【讨论】:

它是否也等于具有相同内容的list?这是在某个地方得到保证,还是只是实现的一个有趣的副作用? @MarkRansom 不是,序列类型之间的相等会导致不平等。我相信在比较参考手册中提到了它,我会尝试找到相关部分并将其添加到答案中。 你可能要注意,这一切只适用于 Python 3。在 2 中,range 返回一个普通列表,xrange 返回一个 xrange 类型的对象,这是不行的智能比较。 xrange(0) == xrange(2, 2, 2) 返回False【参考方案2】:

直接引用the docs(强调我的):

使用 == 和 != 测试范围对象是否相等,将它们比较为 序列。也就是说,两个范围对象被认为是相等的,如果它们 表示相同的值序列。 (注意两个范围对象 比较相等可能有不同的开始,停止和步骤 属性,例如 range(0) == range(2, 1, 3) 或 range(0, 3, 2) == 范围(0, 4, 2).)

如果您将ranges 与“相同”列表进行比较,您将得到不等式,正如the docs 中所述:

不同类型的对象,除了不同的数字类型,从不 比较相等。

例子:

>>> 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

【讨论】:

【参考方案3】:

为了在本页的优秀答案中添加一些额外的细节,将两个range 对象r0r1 进行比较roughly as follows:

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 值执行 simple arithmetic calculation。

所以对于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

【讨论】:

刚刚编辑了我的答案以准确添加此内容,还打算添加源中的引用,但我现在可以将我的答案链接到你的答案。 感谢您的链接,@Jim。您的回答(和其他人)完美地解决了这个问题 - 我的回答实际上只是一个脚注,旨在为可能对 CPython 实现感到好奇的人准备。 是的,我是感兴趣的人之一,当我注意到一个新答案时,实际上打开了 hg.python 选项卡。现在不需要添加它,你已经知道了:-) 在 2. 你的意思是“两个长度是相同的,所以需要另一个测试”? @gnasher729:是的,就是这样。谢谢,为了清楚起见,我会进行编辑。【参考方案4】:

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

地点:

range(0)

表示从00 - 0 步数的范围(这里step 等于默认值1),不列出值。

range(2, 2, 2)

表示从22的范围,步长等于2,没有值的列表。

所以,这些范围实际上是相等的

【讨论】:

【参考方案5】:

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

range(2, 2, 2) 返回range(2, 2, 2)。您从 2 到 2 开始,但步长为 2。这又基本上是 0,因为您不计算任何东西。

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

是的完全相同一样。

【讨论】:

range(2,2,2) 绝不是“基本为 0”

以上是关于为啥 range(0) == range(2, 2, 2) 在 Python 3 中为真?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Range 有效,但 Cells 无效?

为啥 Range 有效,但 Cells 无效?

python中for i in range(0, 3.0 , 0.1)为啥错?

range()

range

使用 for_each 提升 sub_range;为啥我得到一个常量引用?