What is the point of the slice.indices method?

What is the point of the slice.indices method, since we have the following equality?

s = slice(start, stop, step)
assert range(*s.indices(length)) == range(length)[s]

In the old days, range created a list, and xrange didn’t always exist (and was still inferior to what range is now; IIRC, it couldn’t be sliced like that, among other issues). Code that wanted to lazily iterate over a slice could save memory by knowing the start index, stop index and step to use for an explicit manual loop that indexed into the container. Given a slice object and the container’s length, those values are easily calculated, and .indices implements exactly that. So it converts from an abstract representation of a policy for slicing, into a concrete but still indirect representation of the actual indices that would be sliced for a given container. The range would represent those indices more elegantly, but the option wasn’t always available in the past.

Aside from tests, documentation and the actual implementation, I only see one use of it in the Python standard library currently - in pathlib (comments mine):

# Implementation of an internal type that represents all the ancestor paths
# of a given path without storing them explicitly.
class _PathParents(Sequence):
    # other methods omitted
    def __getitem__(self, idx):
        if isinstance(idx, slice):
            # iterate over indices and recurse with integers instead of a slice
            return tuple(self[i] for i in range(*idx.indices(len(self))))
        # otherwise, assume we have an integer index.
        # (If that's wrong, the TypeError will just propagate.)
        if idx >= len(self) or idx < -len(self): # bounds check
            raise IndexError(idx)
        if idx < 0: # wrap negative indices
            idx += len(self)
        # compute the nth ancestor path from the complete path
        return self._path._from_parsed_parts(self._drv, self._root,
                                             self._tail[:-idx - 1])

And, yes, that range(*idx.indices(len(self))) could just as easily be range(len(self))[idx]. There just isn’t a compelling reason to change it at this point. In my own code, I freely refactor that sort of thing. Codebases shared by dozens of developers and depended on by millions of users don’t have the same luxury, even with a full test suite. (I can also imagine that a lot of linters - or even human code reviewers - would see for i in range(len and start screaming on instinct.)

3 Likes

Thanks a lot for your detailed explanation @kknechtel, it’s very clear. Do you mind copying that answer to the same question on Stack Overflow?

I would gladly accept it.

I eventually wrote an answer myself.

1 Like

I no longer write new answers for Stack Overflow (I will do so on Codidact going forward), but please feel free to cite/quote my post here if it can help improve the answer.

Is this something you’re prepared to elaborate on, either in private or in public? I would have asked you in private but you seem to have messaging disabled.

I have been meaning to write about it on my personal blog for some time now, but haven’t gotten around to it (or any other blog articles).

Okay, at least I don’t have to feel bad for asking in public then :slight_smile: I’m definitely curious, since SO’s reputation is… somewhat checkered.

Have you managed to write about it?