为啥 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 进行比较。这实质上意味着比较不评估如何它们代表给定序列,而是它们代表什么。
start
、stop
和 step
参数完全不同的事实在这里没有区别,因为它们在展开时都表示一个空列表:
例如第一个range
对象:
list(range(0)) # []
和第二个range
对象:
list(range(2, 2, 2)) # []
两者都表示一个空列表,并且由于两个空列表比较相等 (True
),所以 表示它们的 range
对象也是如此。
因此,您可以拥有完全不同的外观 range
对象;如果它们代表相同的序列,它们将比较相等:
range(1, 5, 100) == range(1, 30, 100)
两者都表示具有单个元素 [1]
的列表,因此这两个也将比较相等。
不,range
对象真的很特别:
但请注意,即使比较不评估如何它们表示一个序列,比较的结果可以实现使用单独 strong> start
、step
的值以及 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 中用于 EQ
和 NE
情况,并分配给 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).)
如果您将range
s 与“相同”列表进行比较,您将得到不等式,正如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
对象r0
和r1
进行比较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
使用start
、stop
和step
参数可以轻松计算range
对象的长度。例如,在 start == stop
的情况下,Python 可以立即知道长度为 0。在非平凡的情况下,Python 可以使用 start
、stop
和 step
值执行 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)
表示从0
到0
- 0
步数的范围(这里step
等于默认值1
),不列出值。
range(2, 2, 2)
表示从2
到2
的范围,步长等于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 中为真?的主要内容,如果未能解决你的问题,请参考以下文章