summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/functions.rst12
-rw-r--r--Doc/whatsnew/3.3.rst6
-rw-r--r--Lib/test/test_hash.py3
-rw-r--r--Lib/test/test_range.py52
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS4
-rw-r--r--Objects/rangeobject.c135
7 files changed, 209 insertions, 4 deletions
diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst
index 886cb82..6944e1d 100644
--- a/Doc/library/functions.rst
+++ b/Doc/library/functions.rst
@@ -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)
diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst
index d08ee62..95a5583 100644
--- a/Doc/whatsnew/3.3.rst
+++ b/Doc/whatsnew/3.3.rst
@@ -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
=====================================
diff --git a/Lib/test/test_hash.py b/Lib/test/test_hash.py
index fea1025..779e485 100644
--- a/Lib/test/test_hash.py
+++ b/Lib/test/test_hash.py
@@ -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),
]
diff --git a/Lib/test/test_range.py b/Lib/test/test_range.py
index ede0791..6035e76 100644
--- a/Lib/test/test_range.py
+++ b/Lib/test/test_range.py
@@ -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)
diff --git a/Misc/ACKS b/Misc/ACKS
index e3e788e..2a62d96 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -613,6 +613,7 @@ Ken Manheimer
Vladimir Marangozov
David Marek
Doug Marien
+Sven Marnach
Alex Martelli
Anthony Martin
Owen Martin
diff --git a/Misc/NEWS b/Misc/NEWS
index 8e6eff7..e6ee120 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.
diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c
index 58d373c..c1e9e54 100644
--- a/Objects/rangeobject.c
+++ b/Objects/rangeobject.c
@@ -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 */