mirror of
https://github.com/python/cpython.git
synced 2024-11-27 11:55:13 +08:00
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]
|
||||
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)
|
||||
|
||||
|
@ -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
|
||||
=====================================
|
||||
|
||||
|
@ -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),
|
||||
]
|
||||
|
@ -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)
|
||||
|
@ -613,6 +613,7 @@ Ken Manheimer
|
||||
Vladimir Marangozov
|
||||
David Marek
|
||||
Doug Marien
|
||||
Sven Marnach
|
||||
Alex Martelli
|
||||
Anthony Martin
|
||||
Owen Martin
|
||||
|
@ -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.
|
||||
|
@ -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 */
|
||||
|
Loading…
Reference in New Issue
Block a user