1

I have a list:

things = ["a", "b", "c", "d", "e", "f"]

I also have a slice object provided to me, intended for the list above. Here's an example:

s = slice(-2, None, None)
print(things[-2:])  # ['e', 'f']

My goal is to extract the specific indexes the slice impacts. In this example, that would be:

[4, 5]

Does anyone know how I would go about doing this more pythonically? My current solution is:

print([i for i in range(len(things))][s])  # [4, 5]
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Krishnan Shankar
  • 780
  • 9
  • 29
  • I fixed the assignment to `s` such that using it would actually give the corresponding result. `slice(-2, 0, None)` does not work; it would be equivalent to attempting `things[-2:0]`, which gives an empty result. – Karl Knechtel Jun 02 '23 at 01:43
  • Maybe `list(range(len(things))][s])`? – Ynjxsjmh Jun 02 '23 at 01:43
  • This wouldn't work for an example where the slice object has a step. – TheLazyScripter Jun 02 '23 at 01:56
  • @TheLazyScripter as far as I can tell it works just fine, and so do all my suggestions. – Karl Knechtel Jun 02 '23 at 02:05
  • Related (answer almost identical, but question has completely different focus): [Retrieve length of slice from slice object in Python](https://stackoverflow.com/q/36188429/364696) – ShadowRanger Jun 02 '23 at 02:14

1 Answers1

2

This is exactly the purpose of the indices method of the slice class.

As explained in the built-in help:

Help on built-in function indices:

indices(...) method of builtins.slice instance
    S.indices(len) -> (start, stop, stride)
    
    Assuming a sequence of length len, calculate the start and stop
    indices, and the stride length of the extended slice described by
    S. Out of bounds indices are clipped in a manner consistent with the
    handling of normal slices.

Thus, pass it the length of the sequence that will be sliced, in order to get the corresponding start/stop/stride values:

>>> s = slice(-2, None, None)
>>> 
>>> things = ['a', 'b', 'c', 'd', 'e', 'f']
>>> things[s]
['e', 'f']
>>> s.indices(len(things))
(4, 6, 1)
>>> x, y, z = s.indices(len(things))
>>> things[x:y:z]
['e', 'f']

Which can of course be used to construct the corresponding range of values:

>>> list(range(*s.indices(len(things))))
[4, 5]

As an aside, range objects can be indexed and sliced directly, and converting them to lists is as easy as passing them directly to the list type. Thus:

>>> list(range(len(things))[s])
[4, 5]
>>> list(range(len(things)))[s]
[4, 5]

This is simpler for getting an actual list of indices; in other contexts (for example, implementing __getitem__ for a custom type) the start/stop/stride values may be more directly useful.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • 4
    Worth mentioning that `list(range(len(things))[s])` is much more efficient than `list(range(len(things)))[s]` when the size of the slice is much smaller than the size of the input list. – blhsing Jun 02 '23 at 01:55
  • The `indices` method isn't actually helpful here and your "aside" is the actual answer... – Kelly Bundy Jun 02 '23 at 02:39
  • For the output format that was requested, sure; but people often request the wrong output because they simply haven't considered the possibilities. – Karl Knechtel Jun 02 '23 at 02:43
  • Even if that's the case (and I doubt it), I'd say you still have it backwards. The question is what it is, and it's reasonable. I think your current "aside" should come first, and the `indices` method can be an aside following it. – Kelly Bundy Jun 02 '23 at 02:46