Issue #13201: equality for range objects is now based on equality of the underlying sequences. Thanks Sven Marnach for the patch.

This commit is contained in:
Mark Dickinson 2011-10-23 19:53:01 +01:00
parent a2a2e480f3
commit 36645681c8
7 changed files with 209 additions and 4 deletions

View File

@ -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`.
@ -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)

View File

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

View File

@ -107,8 +107,7 @@ class DefaultIterSeq(object):
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),
]

View File

@ -507,6 +507,58 @@ class RangeTest(unittest.TestCase):
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)

View File

@ -613,6 +613,7 @@ Ken Manheimer
Vladimir Marangozov
David Marek
Doug Marien
Sven Marnach
Alex Martelli
Anthony Martin
Owen Martin

View File

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

View File

@ -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)
{
@ -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 */
@ -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 */