9

I was reading python cookbook and came upon this recipe:

If you have a slice instance s, you can get more information about it by looking at its s.start, s.stop, and s.step attributes, respectively. For example:

>>> a = slice(5, 50, 2)
>>> a.start
5
>>> a.stop
50
>>> a.step
2
>>>

In addition, you can map a slice onto a sequence of a specific size by using its indi ces(size) method. This returns a tuple (start, stop, step) where all values have been suitably limited to fit within bounds (as to avoid IndexError exceptions when indexing). For example:

>>> s = 'HelloWorld'
>>> a.indices(len(s))
(5, 10, 2)
>>> for i in range(*a.indices(len(s))):
... print(s[i])
...
W
r
d

I looked up indices() method in Python official documentation but couldn't find it. Is the book making a mistake here? If not, what does this method do?

1

3 Answers 3

6

Calling help(a) on your initialized slice object, I found the following -

 |  indices(...)
 |      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.
Sign up to request clarification or add additional context in comments.

Comments

3

You can find it if you go to the documentation and search for slice.indices in the top-right search box. First result is this:

slice.indices(self, length)
This method takes a single integer argument length and computes information about the slice that the slice object would describe if applied to a sequence of length items. It returns a tuple of three integers; respectively these are the start and stop indices and the step or stride length of the slice. Missing or out-of-bounds indices are handled in a manner consistent with regular slices.

Comments

2

I found the documentation not quite clear, so after understanding what it actually does, I decided to share the explanation in my words:

A Slice object defines start:stop:step values, and is a way to effectively pack those 3 values into a single object (variable):

>>> s = Slice(1, 2)
>>> text[1:2] == text[s]
True

As you can see above, a slice is automatically unpacked, using colon : separators. You can't unpack it using comma , separators and need to do it manually like so:

>>> s.start, s.stop, s.step
(1, 2, None)

Perhaps default values being None is something that should be fixed (default start should be 0, default step should be 1, and you must precise the stop), but it works when slicing, also with literals:

>>> slice(1)
slice(None, 1, None)
>>> "Hello World"[slice(1)]
'H'
>>> "Hello World"[None:1:None]
'H'

However, not only you can't unpack a slice using an asterisk, but also range will not work with None:

>>> s = slice(10, 20)
>>> range(*s)
Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
TypeError: range() argument after * must be an iterable, not slice

>>> range(s.start, s.stop, s.step)
Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
TypeError: 'NoneType' object cannot be interpreted as an integer

You need to replace None with default values:

>>> s = slice(13)
>>> args = (s.start or 0, s.stop, 1 if s.step is None else s.step)
>>> args
(0, 13, 1)

>>> args = (s.start or 0, s.stop, s.step or 1)  # step == 0 is invalid anyway
>>> args
(0, 13, 1)

>>> range(*args)
range(0, 13)

However, while slicing will automatically clamp values to a valid range, using range object and accessing an element using a single integer index, rather than a slice, can cause an IndexError:

>>> text = "Hello World"
>>> s = slice(7, 20)
>>> text[s]
'orld'
>>> for i in range(s.start, s.stop):
...     print(i)
...     print(text[i])
...     
7
o
8
r
9
l
10
d
11

Traceback (most recent call last):
  File "<blender_console>", line 3, in <module>
IndexError: string index out of range

So using slice together with range() require both manual unpacking and clamping to values in bounds. Both problems are solved with slice.indices():

>>> text = "Hello World"
>>> s = slice(7, 20)
>>> [text[i] for i in range(*s.indices(len(text)))]
['o', 'r', 'l', 'd']

slice.indices() expects an argument and doesn't accept float("inf"), so if for some reason you want to resign from clamping, you need to fall back to a previous method or use s.indices(s.stop) if stop is positive (not clamping with negative stop doesn't make sense anyway).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.