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]
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.)
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.
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 I’m definitely curious, since SO’s reputation is… somewhat checkered.