Repository: pandas-dev/pandas

Issue:
Make public `start`, `stop` and `step` attributes for RangeIndex
This may be useful to downstream projects (e.g. https://github.com/apache/arrow/pull/3868).

Initially, I think the idea was for RangeIndex to be a drop-in optimized replacement for the equivalent Int64Index. Other libraries might want public access to the same optimizations.


Discussion:
+1, this would additionally make `RangeIndex` more consistent with the Python 3 `range` type, which has these attributes publicly available:
```python
In [1]: rng = range(2, 11, 3)

In [2]: rng.start
Out[2]: 2

In [3]: rng.stop
Out[3]: 11

In [4]: rng.step
Out[4]: 3
```

Will open a PR for this later tonight.

Resolving patch:
diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py
--- a/pandas/core/indexes/range.py
+++ b/pandas/core/indexes/range.py
@@ -48,7 +48,9 @@ class RangeIndex(Int64Index):
 
     Attributes
     ----------
-    None
+    start
+    stop
+    step
 
     Methods
     -------
@@ -209,6 +211,29 @@ def _format_data(self, name=None):
         return None
 
     # --------------------------------------------------------------------
+    @property
+    def start(self):
+        """
+        The value of the `start` parameter (or ``0`` if this was not supplied)
+        """
+        # GH 25710
+        return self._start
+
+    @property
+    def stop(self):
+        """
+        The value of the `stop` parameter
+        """
+        # GH 25710
+        return self._stop
+
+    @property
+    def step(self):
+        """
+        The value of the `step` parameter (or ``1`` if this was not supplied)
+        """
+        # GH 25710
+        return self._step
 
     @cache_readonly
     def nbytes(self):


Test patch:
diff --git a/pandas/tests/indexes/test_range.py b/pandas/tests/indexes/test_range.py
--- a/pandas/tests/indexes/test_range.py
+++ b/pandas/tests/indexes/test_range.py
@@ -35,47 +35,25 @@ def test_too_many_names(self):
         with pytest.raises(ValueError, match="^Length"):
             self.index.names = ["roger", "harold"]
 
-    def test_constructor(self):
-        index = RangeIndex(5)
-        expected = np.arange(5, dtype=np.int64)
-        assert isinstance(index, RangeIndex)
-        assert index._start == 0
-        assert index._stop == 5
-        assert index._step == 1
-        assert index.name is None
-        tm.assert_index_equal(Index(expected), index)
-
-        index = RangeIndex(1, 5)
-        expected = np.arange(1, 5, dtype=np.int64)
-        assert isinstance(index, RangeIndex)
-        assert index._start == 1
-        tm.assert_index_equal(Index(expected), index)
-
-        index = RangeIndex(1, 5, 2)
-        expected = np.arange(1, 5, 2, dtype=np.int64)
-        assert isinstance(index, RangeIndex)
-        assert index._step == 2
-        tm.assert_index_equal(Index(expected), index)
-
-        for index in [RangeIndex(0), RangeIndex(start=0), RangeIndex(stop=0),
-                      RangeIndex(0, 0)]:
-            expected = np.empty(0, dtype=np.int64)
-            assert isinstance(index, RangeIndex)
-            assert index._start == 0
-            assert index._stop == 0
-            assert index._step == 1
-            tm.assert_index_equal(Index(expected), index)
-
-        for index in [RangeIndex(0, name='Foo'),
-                      RangeIndex(start=0, name='Foo'),
-                      RangeIndex(stop=0, name='Foo'),
-                      RangeIndex(0, 0, name='Foo')]:
-            assert isinstance(index, RangeIndex)
-            assert index.name == 'Foo'
-
-        # we don't allow on a bare Index
-        with pytest.raises(TypeError):
-            Index(0, 1000)
+    @pytest.mark.parametrize('name', [None, 'foo'])
+    @pytest.mark.parametrize('args, kwargs, start, stop, step', [
+        ((5,), dict(), 0, 5, 1),
+        ((1, 5), dict(), 1, 5, 1),
+        ((1, 5, 2), dict(), 1, 5, 2),
+        ((0,), dict(), 0, 0, 1),
+        ((0, 0), dict(), 0, 0, 1),
+        (tuple(), dict(start=0), 0, 0, 1),
+        (tuple(), dict(stop=0), 0, 0, 1)])
+    def test_constructor(self, args, kwargs, start, stop, step, name):
+        result = RangeIndex(*args, name=name, **kwargs)
+        expected = Index(np.arange(start, stop, step, dtype=np.int64),
+                         name=name)
+        assert isinstance(result, RangeIndex)
+        assert result._start == start
+        assert result._stop == stop
+        assert result._step == step
+        assert result.name is name
+        tm.assert_index_equal(result, expected)
 
     def test_constructor_invalid_args(self):
         msg = "RangeIndex\\(\\.\\.\\.\\) must be called with integers"
@@ -92,6 +70,12 @@ def test_constructor_invalid_args(self):
             with pytest.raises(TypeError):
                 RangeIndex(i)
 
+        # we don't allow on a bare Index
+        msg = (r'Index\(\.\.\.\) must be called with a collection of some '
+               r'kind, 0 was passed')
+        with pytest.raises(TypeError, match=msg):
+            Index(0, 1000)
+
     def test_constructor_same(self):
 
         # pass thru w and w/o copy
@@ -172,6 +156,17 @@ def test_constructor_corner(self):
         with pytest.raises(TypeError):
             RangeIndex(1, 5, dtype='float64')
 
+    @pytest.mark.parametrize('index, start, stop, step', [
+        (RangeIndex(5), 0, 5, 1),
+        (RangeIndex(0, 5), 0, 5, 1),
+        (RangeIndex(5, step=2), 0, 5, 2),
+        (RangeIndex(1, 5, 2), 1, 5, 2)])
+    def test_start_stop_step_attrs(self, index, start, stop, step):
+        # GH 25710
+        assert index.start == start
+        assert index.stop == stop
+        assert index.step == step
+
     def test_copy(self):
         i = RangeIndex(5, name='Foo')
         i_copy = i.copy()