Skip to content

Commit

Permalink
Issue python#13201: equality for range objects is now based on equali…
Browse files Browse the repository at this point in the history
…ty of the underlying sequences. Thanks Sven Marnach for the patch.
  • Loading branch information
mdickinson committed Oct 23, 2011
1 parent a2a2e48 commit 3664568
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 4 deletions.
12 changes: 12 additions & 0 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1077,6 +1077,13 @@ are always available. They are listed here in alphabetical order.
>>> r[-1]
18

Testing range objects for equality with ``==`` and ``!=`` compares
them as sequences. That is, two range objects are considered equal if
they represent the same sequence of values. (Note that two range
objects that compare equal might have different :attr:`start`,
:attr:`stop` and :attr:`step` attributes, for example ``range(0) ==
range(2, 1, 3)`` or ``range(0, 3, 2) == range(0, 4, 2)``.)

Ranges containing absolute values larger than :data:`sys.maxsize` are permitted
but some features (such as :func:`len`) will raise :exc:`OverflowError`.

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

.. versionchanged:: 3.3
Define '==' and '!=' to compare range objects based on the
sequence of values they define (instead of comparing based on
object identity).


.. function:: repr(object)

Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ and :func:`unicodedata.lookup()` resolves named sequences too.
(Contributed by Ezio Melotti in :issue:`12753`)


Equality comparisons on :func:`range` objects now return a result reflecting
the equality of the underlying sequences generated by those range objects.

(:issue:`13021`)


New, Improved, and Deprecated Modules
=====================================

Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ def __getitem__(self, index):
return self.seq[index]

class HashBuiltinsTestCase(unittest.TestCase):
hashes_to_check = [range(10),
enumerate(range(10)),
hashes_to_check = [enumerate(range(10)),
iter(DefaultIterSeq()),
iter(lambda: 0, 0),
]
Expand Down
52 changes: 52 additions & 0 deletions Lib/test/test_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,58 @@ def test_issue11845(self):
for k in values - {0}:
r[i:j:k]

def test_comparison(self):
test_ranges = [range(0), range(0, -1), range(1, 1, 3),
range(1), range(5, 6), range(5, 6, 2),
range(5, 7, 2), range(2), range(0, 4, 2),
range(0, 5, 2), range(0, 6, 2)]
test_tuples = list(map(tuple, test_ranges))

# Check that equality of ranges matches equality of the corresponding
# tuples for each pair from the test lists above.
ranges_eq = [a == b for a in test_ranges for b in test_ranges]
tuples_eq = [a == b for a in test_tuples for b in test_tuples]
self.assertEqual(ranges_eq, tuples_eq)

# Check that != correctly gives the logical negation of ==
ranges_ne = [a != b for a in test_ranges for b in test_ranges]
self.assertEqual(ranges_ne, [not x for x in ranges_eq])

# Equal ranges should have equal hashes.
for a in test_ranges:
for b in test_ranges:
if a == b:
self.assertEqual(hash(a), hash(b))

# Ranges are unequal to other types (even sequence types)
self.assertIs(range(0) == (), False)
self.assertIs(() == range(0), False)
self.assertIs(range(2) == [0, 1], False)

# Huge integers aren't a problem.
self.assertEqual(range(0, 2**100 - 1, 2),
range(0, 2**100, 2))
self.assertEqual(hash(range(0, 2**100 - 1, 2)),
hash(range(0, 2**100, 2)))
self.assertNotEqual(range(0, 2**100, 2),
range(0, 2**100 + 1, 2))
self.assertEqual(range(2**200, 2**201 - 2**99, 2**100),
range(2**200, 2**201, 2**100))
self.assertEqual(hash(range(2**200, 2**201 - 2**99, 2**100)),
hash(range(2**200, 2**201, 2**100)))
self.assertNotEqual(range(2**200, 2**201, 2**100),
range(2**200, 2**201 + 1, 2**100))

# Order comparisons are not implemented for ranges.
with self.assertRaises(TypeError):
range(0) < range(0)
with self.assertRaises(TypeError):
range(0) > range(0)
with self.assertRaises(TypeError):
range(0) <= range(0)
with self.assertRaises(TypeError):
range(0) >= range(0)


def test_main():
test.support.run_unittest(RangeTest)
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,7 @@ Ken Manheimer
Vladimir Marangozov
David Marek
Doug Marien
Sven Marnach
Alex Martelli
Anthony Martin
Owen Martin
Expand Down
4 changes: 4 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ What's New in Python 3.3 Alpha 1?
Core and Builtins
-----------------

- Issue #13201: Define '==' and '!=' to compare range objects based on
the sequence of values they define (instead of comparing based on
object identity).

- Issue #1294232: In a few cases involving metaclass inheritance, the
interpreter would sometimes invoke the wrong metaclass when building a new
class object. These cases now behave correctly. Patch by Daniel Urban.
Expand Down
135 changes: 133 additions & 2 deletions Objects/rangeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,137 @@ range_contains(rangeobject *r, PyObject *ob)
PY_ITERSEARCH_CONTAINS);
}

/* Compare two range objects. Return 1 for equal, 0 for not equal
and -1 on error. The algorithm is roughly the C equivalent of
if r0 is r1:
return True
if len(r0) != len(r1):
return False
if not len(r0):
return True
if r0.start != r1.start:
return False
if len(r0) == 1:
return True
return r0.step == r1.step
*/
static int
range_equals(rangeobject *r0, rangeobject *r1)
{
int cmp_result;
PyObject *one;

if (r0 == r1)
return 1;
cmp_result = PyObject_RichCompareBool(r0->length, r1->length, Py_EQ);
/* Return False or error to the caller. */
if (cmp_result != 1)
return cmp_result;
cmp_result = PyObject_Not(r0->length);
/* Return True or error to the caller. */
if (cmp_result != 0)
return cmp_result;
cmp_result = PyObject_RichCompareBool(r0->start, r1->start, Py_EQ);
/* Return False or error to the caller. */
if (cmp_result != 1)
return cmp_result;
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;
return PyObject_RichCompareBool(r0->step, r1->step, Py_EQ);
}

static PyObject *
range_richcompare(PyObject *self, PyObject *other, int op)
{
int result;

if (!PyRange_Check(other))
Py_RETURN_NOTIMPLEMENTED;
switch (op) {
case Py_NE:
case Py_EQ:
result = range_equals((rangeobject*)self, (rangeobject*)other);
if (result == -1)
return NULL;
if (op == Py_NE)
result = !result;
if (result)
Py_RETURN_TRUE;
else
Py_RETURN_FALSE;
case Py_LE:
case Py_GE:
case Py_LT:
case Py_GT:
Py_RETURN_NOTIMPLEMENTED;
default:
PyErr_BadArgument();
return NULL;
}
}

/* Hash function for range objects. Rough C equivalent of
if not len(r):
return hash((len(r), None, None))
if len(r) == 1:
return hash((len(r), r.start, None))
return hash((len(r), r.start, r.step))
*/
static Py_hash_t
range_hash(rangeobject *r)
{
PyObject *t;
Py_hash_t result = -1;
int cmp_result;

t = PyTuple_New(3);
if (!t)
return -1;
Py_INCREF(r->length);
PyTuple_SET_ITEM(t, 0, r->length);
cmp_result = PyObject_Not(r->length);
if (cmp_result == -1)
goto end;
if (cmp_result == 1) {
Py_INCREF(Py_None);
Py_INCREF(Py_None);
PyTuple_SET_ITEM(t, 1, Py_None);
PyTuple_SET_ITEM(t, 2, Py_None);
}
else {
PyObject *one;
Py_INCREF(r->start);
PyTuple_SET_ITEM(t, 1, r->start);
one = PyLong_FromLong(1);
if (!one)
goto end;
cmp_result = PyObject_RichCompareBool(r->length, one, Py_EQ);
Py_DECREF(one);
if (cmp_result == -1)
goto end;
if (cmp_result == 1) {
Py_INCREF(Py_None);
PyTuple_SET_ITEM(t, 2, Py_None);
}
else {
Py_INCREF(r->step);
PyTuple_SET_ITEM(t, 2, r->step);
}
}
result = PyObject_Hash(t);
end:
Py_DECREF(t);
return result;
}

static PyObject *
range_count(rangeobject *r, PyObject *ob)
{
Expand Down Expand Up @@ -763,7 +894,7 @@ PyTypeObject PyRange_Type = {
0, /* tp_as_number */
&range_as_sequence, /* tp_as_sequence */
&range_as_mapping, /* tp_as_mapping */
0, /* tp_hash */
(hashfunc)range_hash, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
Expand All @@ -773,7 +904,7 @@ PyTypeObject PyRange_Type = {
range_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
range_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
range_iter, /* tp_iter */
0, /* tp_iternext */
Expand Down

0 comments on commit 3664568

Please sign in to comment.