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

新手上路,请多包涵

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

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

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

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

原文由 root 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 636
2 个回答

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 对象 真的很 特别:

但请注意,即使比较不评估它们 如何 表示序列, 也可以 使用 startstep 的值以及 len range 对象;这对比较速度有非常有趣的影响:

 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 对象。

直接从 Python 3 range 对象的源代码中查看 @ajcr 的引用答案。那里记录了两个不同范围之间的比较实际上需要什么:简单快速的操作。 The range_equals function is utilized in the _range_richcompare function_ for EQ and NE cases and assigned to the _tp_richcompare slot对于 PyRange_Type 类型_。

我相信 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 等效项的回答_。

原文由 Dimitris Fasarakis Hilliard 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

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

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

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

例子:

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

原文由 Jasper 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题