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

roo*_*oot 53 python identity range python-3.x python-internals

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

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

>>> r1 = range(0)
>>> r2 = range(2, 2, 2)
>>> r1 == r2
True
Run Code Online (Sandbox Code Playgroud)

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

Jim*_*ard 72

range对象是特殊的:

Python会将range对象比较为Sequences.这实际上意味着比较不会评估它们如何代表给定的序列,而是它们代表什么.

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

例如,第一个range对象:

list(range(0))  # []
Run Code Online (Sandbox Code Playgroud)

和第二个range对象:

list(range(2, 2, 2)) # []
Run Code Online (Sandbox Code Playgroud)

两者都代表一个空列表,因为两个空列表比较equal(True),所以代表它们的range对象也是如此.

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

range(1, 5, 100) == range(1, 30, 100) 
Run Code Online (Sandbox Code Playgroud)

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


不,range对象真的很特别:

做笔记,虽然,虽然比较不评估怎么他们所代表的比较结果的顺序可以实现使用单独的值start,step与沿len的的range对象; 这与比较的速度有非常有趣的含义:

r0 = range(1, 1000000)    
r1 = range(1, 1000000)

l0 = list(r0)    
l1 = list(r1)
Run Code Online (Sandbox Code Playgroud)

范围比较超快:

%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
Run Code Online (Sandbox Code Playgroud)

另一方面,列表..

%timeit l0 == l1
10 loops, best of 3: 27.8 ms per loop
Run Code Online (Sandbox Code Playgroud)

是啊..


正如@SuperBiasedMan指出的那样,这仅适用于在Python 3的Python范围对象2 range()是一个普通的醇"函数返回一个列表,而2.x xrange对象不具有所述比较capabilies(不仅这些..),该range对象具有在Python 3.

直接从Python 3 对象的源代码中查看@ ajcr的引号答案range.在那里记录了两个不同范围之间的比较实际需要:简单的快速操作.的range_equals功能被用在range_richcompare功能EQNE箱子和分配给该tp_richcompare对槽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);
Run Code Online (Sandbox Code Playgroud)

我也在这里散布了一些自己的评论; 看看@ ajcr的 Python等效答案.

  • 您可能需要注意,这一切仅适用于Python 3.在2中,`range`返回一个普通列表,而`xrange`返回一个`xrange`类型对象,它不进行智能比较.`xrange(0)== xrange(2,2,2)`返回`False`. (3认同)
  • @MarkRansom它不会,序列类型之间的相等会导致不平等.我相信在比较参考手册中提到它,我会尝试找到相关部分并将其添加到答案中. (2认同)

Jas*_*per 13

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

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

如果你将ranges与"相同"列表进行比较,你会得到不平等,如文档所述:

除了不同的数字类型之外,不同类型的对象永远不会相等.

例:

>>> type(range(1))
<class 'range'>
>>> type([0])
<class 'list'>
>>> [0] == range(1)
False
>>> [0] == list(range(1))
True
Run Code Online (Sandbox Code Playgroud)

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


Ale*_*ley 10

要添加一些额外的细节,以优良的答案,此页面上有两个range对象r0,并r1进行比较大致如下:

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
Run Code Online (Sandbox Code Playgroud)

一个的长度range目的是容易地使用所述计算start,stopstep参数.在的情况下start == stop,例如,Python可以立即知道的长度为0.在非平凡的情况下,Python可以只是做一个简单的算术计算使用start,stopstep的值.

所以在这种情况下range(0) == range(2, 2, 2),Python会执行以下操作:

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


piv*_*chy 6

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

哪里:

range(0)
Run Code Online (Sandbox Code Playgroud)

表示从- 0到步骤(此处等于默认值)的范围,没有值的列表.00step1

range(2, 2, 2)
Run Code Online (Sandbox Code Playgroud)

指的是从范围22与步骤等于2,列表而不值.

所以,这些范围真的相等


lox*_*sat 5

range(0)回报range(0,0).从步骤1开始,从0开始到0,这是未定义的,因为第三个参数不能为0 [默认].没有计数器的动作,因此0不能达到0.

range(2, 2, 2)回报range(2, 2, 2).你从2开始到2但是步长为2.再次,基本上是0,因为你没有计算任何东西.

range(0) == range(2,2,2) 
Run Code Online (Sandbox Code Playgroud)

真实完全一样.

  • `range(2,2,2)`绝对不是"基本上0" (2认同)