Skip to content

Commit 3664568

Browse files
committed
Issue python#13201: equality for range objects is now based on equality of the underlying sequences. Thanks Sven Marnach for the patch.
1 parent a2a2e48 commit 3664568

7 files changed

Lines changed: 209 additions & 4 deletions

File tree

Doc/library/functions.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,13 @@ are always available. They are listed here in alphabetical order.
10771077
>>> r[-1]
10781078
18
10791079

1080+
Testing range objects for equality with ``==`` and ``!=`` compares
1081+
them as sequences. That is, two range objects are considered equal if
1082+
they represent the same sequence of values. (Note that two range
1083+
objects that compare equal might have different :attr:`start`,
1084+
:attr:`stop` and :attr:`step` attributes, for example ``range(0) ==
1085+
range(2, 1, 3)`` or ``range(0, 3, 2) == range(0, 4, 2)``.)
1086+
10801087
Ranges containing absolute values larger than :data:`sys.maxsize` are permitted
10811088
but some features (such as :func:`len`) will raise :exc:`OverflowError`.
10821089

@@ -1086,6 +1093,11 @@ are always available. They are listed here in alphabetical order.
10861093
Test integers for membership in constant time instead of iterating
10871094
through all items.
10881095

1096+
.. versionchanged:: 3.3
1097+
Define '==' and '!=' to compare range objects based on the
1098+
sequence of values they define (instead of comparing based on
1099+
object identity).
1100+
10891101

10901102
.. function:: repr(object)
10911103

Doc/whatsnew/3.3.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ and :func:`unicodedata.lookup()` resolves named sequences too.
186186
(Contributed by Ezio Melotti in :issue:`12753`)
187187

188188

189+
Equality comparisons on :func:`range` objects now return a result reflecting
190+
the equality of the underlying sequences generated by those range objects.
191+
192+
(:issue:`13021`)
193+
194+
189195
New, Improved, and Deprecated Modules
190196
=====================================
191197

Lib/test/test_hash.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ def __getitem__(self, index):
107107
return self.seq[index]
108108

109109
class HashBuiltinsTestCase(unittest.TestCase):
110-
hashes_to_check = [range(10),
111-
enumerate(range(10)),
110+
hashes_to_check = [enumerate(range(10)),
112111
iter(DefaultIterSeq()),
113112
iter(lambda: 0, 0),
114113
]

Lib/test/test_range.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,58 @@ def test_issue11845(self):
507507
for k in values - {0}:
508508
r[i:j:k]
509509

510+
def test_comparison(self):
511+
test_ranges = [range(0), range(0, -1), range(1, 1, 3),
512+
range(1), range(5, 6), range(5, 6, 2),
513+
range(5, 7, 2), range(2), range(0, 4, 2),
514+
range(0, 5, 2), range(0, 6, 2)]
515+
test_tuples = list(map(tuple, test_ranges))
516+
517+
# Check that equality of ranges matches equality of the corresponding
518+
# tuples for each pair from the test lists above.
519+
ranges_eq = [a == b for a in test_ranges for b in test_ranges]
520+
tuples_eq = [a == b for a in test_tuples for b in test_tuples]
521+
self.assertEqual(ranges_eq, tuples_eq)
522+
523+
# Check that != correctly gives the logical negation of ==
524+
ranges_ne = [a != b for a in test_ranges for b in test_ranges]
525+
self.assertEqual(ranges_ne, [not x for x in ranges_eq])
526+
527+
# Equal ranges should have equal hashes.
528+
for a in test_ranges:
529+
for b in test_ranges:
530+
if a == b:
531+
self.assertEqual(hash(a), hash(b))
532+
533+
# Ranges are unequal to other types (even sequence types)
534+
self.assertIs(range(0) == (), False)
535+
self.assertIs(() == range(0), False)
536+
self.assertIs(range(2) == [0, 1], False)
537+
538+
# Huge integers aren't a problem.
539+
self.assertEqual(range(0, 2**100 - 1, 2),
540+
range(0, 2**100, 2))
541+
self.assertEqual(hash(range(0, 2**100 - 1, 2)),
542+
hash(range(0, 2**100, 2)))
543+
self.assertNotEqual(range(0, 2**100, 2),
544+
range(0, 2**100 + 1, 2))
545+
self.assertEqual(range(2**200, 2**201 - 2**99, 2**100),
546+
range(2**200, 2**201, 2**100))
547+
self.assertEqual(hash(range(2**200, 2**201 - 2**99, 2**100)),
548+
hash(range(2**200, 2**201, 2**100)))
549+
self.assertNotEqual(range(2**200, 2**201, 2**100),
550+
range(2**200, 2**201 + 1, 2**100))
551+
552+
# Order comparisons are not implemented for ranges.
553+
with self.assertRaises(TypeError):
554+
range(0) < range(0)
555+
with self.assertRaises(TypeError):
556+
range(0) > range(0)
557+
with self.assertRaises(TypeError):
558+
range(0) <= range(0)
559+
with self.assertRaises(TypeError):
560+
range(0) >= range(0)
561+
510562

511563
def test_main():
512564
test.support.run_unittest(RangeTest)

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ Ken Manheimer
613613
Vladimir Marangozov
614614
David Marek
615615
Doug Marien
616+
Sven Marnach
616617
Alex Martelli
617618
Anthony Martin
618619
Owen Martin

Misc/NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ What's New in Python 3.3 Alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #13201: Define '==' and '!=' to compare range objects based on
14+
the sequence of values they define (instead of comparing based on
15+
object identity).
16+
1317
- Issue #1294232: In a few cases involving metaclass inheritance, the
1418
interpreter would sometimes invoke the wrong metaclass when building a new
1519
class object. These cases now behave correctly. Patch by Daniel Urban.

Objects/rangeobject.c

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,137 @@ range_contains(rangeobject *r, PyObject *ob)
609609
PY_ITERSEARCH_CONTAINS);
610610
}
611611

612+
/* Compare two range objects. Return 1 for equal, 0 for not equal
613+
and -1 on error. The algorithm is roughly the C equivalent of
614+
615+
if r0 is r1:
616+
return True
617+
if len(r0) != len(r1):
618+
return False
619+
if not len(r0):
620+
return True
621+
if r0.start != r1.start:
622+
return False
623+
if len(r0) == 1:
624+
return True
625+
return r0.step == r1.step
626+
*/
627+
static int
628+
range_equals(rangeobject *r0, rangeobject *r1)
629+
{
630+
int cmp_result;
631+
PyObject *one;
632+
633+
if (r0 == r1)
634+
return 1;
635+
cmp_result = PyObject_RichCompareBool(r0->length, r1->length, Py_EQ);
636+
/* Return False or error to the caller. */
637+
if (cmp_result != 1)
638+
return cmp_result;
639+
cmp_result = PyObject_Not(r0->length);
640+
/* Return True or error to the caller. */
641+
if (cmp_result != 0)
642+
return cmp_result;
643+
cmp_result = PyObject_RichCompareBool(r0->start, r1->start, Py_EQ);
644+
/* Return False or error to the caller. */
645+
if (cmp_result != 1)
646+
return cmp_result;
647+
one = PyLong_FromLong(1);
648+
if (!one)
649+
return -1;
650+
cmp_result = PyObject_RichCompareBool(r0->length, one, Py_EQ);
651+
Py_DECREF(one);
652+
/* Return True or error to the caller. */
653+
if (cmp_result != 0)
654+
return cmp_result;
655+
return PyObject_RichCompareBool(r0->step, r1->step, Py_EQ);
656+
}
657+
658+
static PyObject *
659+
range_richcompare(PyObject *self, PyObject *other, int op)
660+
{
661+
int result;
662+
663+
if (!PyRange_Check(other))
664+
Py_RETURN_NOTIMPLEMENTED;
665+
switch (op) {
666+
case Py_NE:
667+
case Py_EQ:
668+
result = range_equals((rangeobject*)self, (rangeobject*)other);
669+
if (result == -1)
670+
return NULL;
671+
if (op == Py_NE)
672+
result = !result;
673+
if (result)
674+
Py_RETURN_TRUE;
675+
else
676+
Py_RETURN_FALSE;
677+
case Py_LE:
678+
case Py_GE:
679+
case Py_LT:
680+
case Py_GT:
681+
Py_RETURN_NOTIMPLEMENTED;
682+
default:
683+
PyErr_BadArgument();
684+
return NULL;
685+
}
686+
}
687+
688+
/* Hash function for range objects. Rough C equivalent of
689+
690+
if not len(r):
691+
return hash((len(r), None, None))
692+
if len(r) == 1:
693+
return hash((len(r), r.start, None))
694+
return hash((len(r), r.start, r.step))
695+
*/
696+
static Py_hash_t
697+
range_hash(rangeobject *r)
698+
{
699+
PyObject *t;
700+
Py_hash_t result = -1;
701+
int cmp_result;
702+
703+
t = PyTuple_New(3);
704+
if (!t)
705+
return -1;
706+
Py_INCREF(r->length);
707+
PyTuple_SET_ITEM(t, 0, r->length);
708+
cmp_result = PyObject_Not(r->length);
709+
if (cmp_result == -1)
710+
goto end;
711+
if (cmp_result == 1) {
712+
Py_INCREF(Py_None);
713+
Py_INCREF(Py_None);
714+
PyTuple_SET_ITEM(t, 1, Py_None);
715+
PyTuple_SET_ITEM(t, 2, Py_None);
716+
}
717+
else {
718+
PyObject *one;
719+
Py_INCREF(r->start);
720+
PyTuple_SET_ITEM(t, 1, r->start);
721+
one = PyLong_FromLong(1);
722+
if (!one)
723+
goto end;
724+
cmp_result = PyObject_RichCompareBool(r->length, one, Py_EQ);
725+
Py_DECREF(one);
726+
if (cmp_result == -1)
727+
goto end;
728+
if (cmp_result == 1) {
729+
Py_INCREF(Py_None);
730+
PyTuple_SET_ITEM(t, 2, Py_None);
731+
}
732+
else {
733+
Py_INCREF(r->step);
734+
PyTuple_SET_ITEM(t, 2, r->step);
735+
}
736+
}
737+
result = PyObject_Hash(t);
738+
end:
739+
Py_DECREF(t);
740+
return result;
741+
}
742+
612743
static PyObject *
613744
range_count(rangeobject *r, PyObject *ob)
614745
{
@@ -763,7 +894,7 @@ PyTypeObject PyRange_Type = {
763894
0, /* tp_as_number */
764895
&range_as_sequence, /* tp_as_sequence */
765896
&range_as_mapping, /* tp_as_mapping */
766-
0, /* tp_hash */
897+
(hashfunc)range_hash, /* tp_hash */
767898
0, /* tp_call */
768899
0, /* tp_str */
769900
PyObject_GenericGetAttr, /* tp_getattro */
@@ -773,7 +904,7 @@ PyTypeObject PyRange_Type = {
773904
range_doc, /* tp_doc */
774905
0, /* tp_traverse */
775906
0, /* tp_clear */
776-
0, /* tp_richcompare */
907+
range_richcompare, /* tp_richcompare */
777908
0, /* tp_weaklistoffset */
778909
range_iter, /* tp_iter */
779910
0, /* tp_iternext */

0 commit comments

Comments
 (0)