mirror of https://github.com/python/cpython
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:
parent
a2a2e480f3
commit
36645681c8
|
@ -1077,6 +1077,13 @@ are always available. They are listed here in alphabetical order.
|
||||||
>>> r[-1]
|
>>> r[-1]
|
||||||
18
|
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
|
Ranges containing absolute values larger than :data:`sys.maxsize` are permitted
|
||||||
but some features (such as :func:`len`) will raise :exc:`OverflowError`.
|
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
|
Test integers for membership in constant time instead of iterating
|
||||||
through all items.
|
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)
|
.. function:: repr(object)
|
||||||
|
|
||||||
|
|
|
@ -186,6 +186,12 @@ and :func:`unicodedata.lookup()` resolves named sequences too.
|
||||||
(Contributed by Ezio Melotti in :issue:`12753`)
|
(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
|
New, Improved, and Deprecated Modules
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -107,8 +107,7 @@ class DefaultIterSeq(object):
|
||||||
return self.seq[index]
|
return self.seq[index]
|
||||||
|
|
||||||
class HashBuiltinsTestCase(unittest.TestCase):
|
class HashBuiltinsTestCase(unittest.TestCase):
|
||||||
hashes_to_check = [range(10),
|
hashes_to_check = [enumerate(range(10)),
|
||||||
enumerate(range(10)),
|
|
||||||
iter(DefaultIterSeq()),
|
iter(DefaultIterSeq()),
|
||||||
iter(lambda: 0, 0),
|
iter(lambda: 0, 0),
|
||||||
]
|
]
|
||||||
|
|
|
@ -507,6 +507,58 @@ class RangeTest(unittest.TestCase):
|
||||||
for k in values - {0}:
|
for k in values - {0}:
|
||||||
r[i:j:k]
|
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():
|
def test_main():
|
||||||
test.support.run_unittest(RangeTest)
|
test.support.run_unittest(RangeTest)
|
||||||
|
|
|
@ -613,6 +613,7 @@ Ken Manheimer
|
||||||
Vladimir Marangozov
|
Vladimir Marangozov
|
||||||
David Marek
|
David Marek
|
||||||
Doug Marien
|
Doug Marien
|
||||||
|
Sven Marnach
|
||||||
Alex Martelli
|
Alex Martelli
|
||||||
Anthony Martin
|
Anthony Martin
|
||||||
Owen Martin
|
Owen Martin
|
||||||
|
|
|
@ -10,6 +10,10 @@ What's New in Python 3.3 Alpha 1?
|
||||||
Core and Builtins
|
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
|
- Issue #1294232: In a few cases involving metaclass inheritance, the
|
||||||
interpreter would sometimes invoke the wrong metaclass when building a new
|
interpreter would sometimes invoke the wrong metaclass when building a new
|
||||||
class object. These cases now behave correctly. Patch by Daniel Urban.
|
class object. These cases now behave correctly. Patch by Daniel Urban.
|
||||||
|
|
|
@ -609,6 +609,137 @@ range_contains(rangeobject *r, PyObject *ob)
|
||||||
PY_ITERSEARCH_CONTAINS);
|
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 *
|
static PyObject *
|
||||||
range_count(rangeobject *r, PyObject *ob)
|
range_count(rangeobject *r, PyObject *ob)
|
||||||
{
|
{
|
||||||
|
@ -763,7 +894,7 @@ PyTypeObject PyRange_Type = {
|
||||||
0, /* tp_as_number */
|
0, /* tp_as_number */
|
||||||
&range_as_sequence, /* tp_as_sequence */
|
&range_as_sequence, /* tp_as_sequence */
|
||||||
&range_as_mapping, /* tp_as_mapping */
|
&range_as_mapping, /* tp_as_mapping */
|
||||||
0, /* tp_hash */
|
(hashfunc)range_hash, /* tp_hash */
|
||||||
0, /* tp_call */
|
0, /* tp_call */
|
||||||
0, /* tp_str */
|
0, /* tp_str */
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
|
@ -773,7 +904,7 @@ PyTypeObject PyRange_Type = {
|
||||||
range_doc, /* tp_doc */
|
range_doc, /* tp_doc */
|
||||||
0, /* tp_traverse */
|
0, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
0, /* tp_richcompare */
|
range_richcompare, /* tp_richcompare */
|
||||||
0, /* tp_weaklistoffset */
|
0, /* tp_weaklistoffset */
|
||||||
range_iter, /* tp_iter */
|
range_iter, /* tp_iter */
|
||||||
0, /* tp_iternext */
|
0, /* tp_iternext */
|
||||||
|
|
Loading…
Reference in New Issue