pythonpython-3.xrangeslice

Turn slice into range


I'm using Python 3.3. I want to get a slice object and use it to make a new range object.

It goes something like that:

>>> class A:
    def __getitem__(self, item):
        if isinstance(item, slice):
            return list(range(item.start, item.stop, item.step))

>>> a = A()
>>> a[1:5:2] # works fine
[1, 3]
>>> a[1:5] # won't work :(
Traceback (most recent call last):
  File "<pyshell#18>", line 1, in <module>
    a[1:5] # won't work :(
  File "<pyshell#9>", line 4, in __getitem__
    return list(range(item.start, item.stop, item.step))
TypeError: 'NoneType' object cannot be interpreted as an integer

Well, the problem is obvious here - range doesn't accept None as a value:

>>> range(1, 5, None)
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    range(1, 5, None)
TypeError: 'NoneType' object cannot be interpreted as an integer

But what is not obvious (to me) is the solution. How will I call range so it will work in every case? I'm searching for a nice pythonic way to do it.


Solution

  • Try

    def ifnone(a, b):
        return b if a is None else a
    
    class A:
        def __getitem__(self, item):
            if isinstance(item, slice):
                if item.stop is None:
                    # do something with itertools.count()
                else:
                    return list(range(ifnone(item.start, 0), item.stop, ifnone(item.step, 1)))
            else:
                return item
    

    This will reinterpret .start and .step appropriately if they are None.


    Another option could be the .indices() method of a slice. It is called with the number of entries and reinterprets None to the appropriate values and wraps negative values around the given length parameter:

    >>> a = slice(None, None, None)
    >>> a.indices(1)
    (0, 1, 1)
    >>> a.indices(10)
    (0, 10, 1)
    >>> a = slice(None, -5, None)
    >>> a.indices(100)
    (0, 95, 1)
    

    It depends what you intend to do with negative indices …


    Edit 2024:

    The easiest way is probably

    class A:
        def __init__(self, length):
            self._length = length
        def __len__(self):
            return self._length
        def __getitem__(self, item):
            if isinstance(item, slice):
                return range(*item.indices(self._length))
            else:
                return item
    

    Here you have to provide a length when creating A() which is then used if the slicing needs it.