diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst index f9af3d81152..3cf53357639 100644 --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -1042,7 +1042,9 @@ are always available. They are listed here in alphabetical order. ...]``. If *step* is positive, the last element is the largest ``start + i * step`` less than *stop*; if *step* is negative, the last element is the smallest ``start + i * step`` greater than *stop*. *step* must not be zero - (or else :exc:`ValueError` is raised). Example: + (or else :exc:`ValueError` is raised). Range objects have read-only data + attributes :attr:`start`, :attr:`stop` and :attr:`step` which return the + argument values (or their default). Example: >>> list(range(10)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] @@ -1100,6 +1102,9 @@ are always available. They are listed here in alphabetical order. sequence of values they define (instead of comparing based on object identity). + .. versionadded:: 3.3 + The :attr:`start`, :attr:`stop` and :attr:`step` attributes. + .. function:: repr(object) diff --git a/Lib/test/test_range.py b/Lib/test/test_range.py index 6035e76481c..2e335cc70d1 100644 --- a/Lib/test/test_range.py +++ b/Lib/test/test_range.py @@ -560,6 +560,35 @@ class RangeTest(unittest.TestCase): range(0) >= range(0) + def test_attributes(self): + # test the start, stop and step attributes of range objects + self.assert_attrs(range(0), 0, 0, 1) + self.assert_attrs(range(10), 0, 10, 1) + self.assert_attrs(range(-10), 0, -10, 1) + self.assert_attrs(range(0, 10, 1), 0, 10, 1) + self.assert_attrs(range(0, 10, 3), 0, 10, 3) + self.assert_attrs(range(10, 0, -1), 10, 0, -1) + self.assert_attrs(range(10, 0, -3), 10, 0, -3) + + def assert_attrs(self, rangeobj, start, stop, step): + self.assertEqual(rangeobj.start, start) + self.assertEqual(rangeobj.stop, stop) + self.assertEqual(rangeobj.step, step) + + with self.assertRaises(AttributeError): + rangeobj.start = 0 + with self.assertRaises(AttributeError): + rangeobj.stop = 10 + with self.assertRaises(AttributeError): + rangeobj.step = 1 + + with self.assertRaises(AttributeError): + del rangeobj.start + with self.assertRaises(AttributeError): + del rangeobj.stop + with self.assertRaises(AttributeError): + del rangeobj.step + def test_main(): test.support.run_unittest(RangeTest) diff --git a/Misc/NEWS b/Misc/NEWS index 47da8190e08..06551c05b47 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,8 @@ What's New in Python 3.3 Alpha 1? Core and Builtins ----------------- +- Issue #9896: Add start, stop, and step attributes to range objects. + - Issue #13343: Fix a SystemError when a lambda expression uses a global variable in the default value of a keyword-only argument: (lambda *, arg=GLOBAL_NAME: None) diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index c1e9e543030..fb6a5fe02d1 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -1,6 +1,7 @@ /* Range object implementation */ #include "Python.h" +#include "structmember.h" /* Support objects whose length is > PY_SSIZE_T_MAX. @@ -880,6 +881,13 @@ static PyMethodDef range_methods[] = { {NULL, NULL} /* sentinel */ }; +static PyMemberDef range_members[] = { + {"start", T_OBJECT_EX, offsetof(rangeobject, start), READONLY}, + {"stop", T_OBJECT_EX, offsetof(rangeobject, stop), READONLY}, + {"step", T_OBJECT_EX, offsetof(rangeobject, step), READONLY}, + {0} +}; + PyTypeObject PyRange_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "range", /* Name of this type */ @@ -909,7 +917,7 @@ PyTypeObject PyRange_Type = { range_iter, /* tp_iter */ 0, /* tp_iternext */ range_methods, /* tp_methods */ - 0, /* tp_members */ + range_members, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */