8

A slice in python is not iterable. This code:

s = slice(1, 10, 2)
iter(s)

results in this error:

TypeError: 'slice' object is not iterable

This is the code I've come up with to show the slice by creating a list iterable:

list(range(s.start, s.stop, s.step))

This uses the start, stop and step attributes of the slice object. I plug those into a range (an immutable sequence type) and create a list:

[1, 3, 5, 7, 9]

Is there something missing? Can I iterate over a slice any better?

Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
JoePythonKing
  • 1,080
  • 1
  • 9
  • 18
  • 1
    Shouldn't you simply need a `range(1, 10, 2)`? – Austin Mar 01 '19 at 09:24
  • Range is more or less very similar to a slice, so I could use a range to generate that sequence but I was focused on penetrating my understanding of what a slice is and how to manipulate it. Didn't the slice come first? Then range? Range seems to be a generator, more memory efficient. – JoePythonKing Mar 01 '19 at 17:55
  • Also, the original question has a practical relevance when implementing `__setitem__`, where you want to support slices. – Ken Seehart Aug 25 '20 at 12:56
  • `range(s.start, s.stop, s.step)` doesn't work when s.start or s.step are None. – Ken Seehart Aug 25 '20 at 12:59

4 Answers4

5

A slice isn't an iterable. It doesn't contain elements, but instead specifies which elements in some other iterable are to be returned if the slice is applied to that iterable.

Since it's not an iterable, you can't iterate over it. As you have discovered, however, you can obtain the indices for which it will return elements from an iterable to which it is applied, using range() - and you can iterate over that:

s = slice(1, 10, 2)
indices = range(s.start, s.stop, s.step)
it = iter(indices)

>>> list(it)
[1, 3, 5, 7, 9]
Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
  • I had a thought about subclassing slice(). class Myslice(slice): then def __getitem__(self,key) but I don't know what else to put in it. At the moment it is above my level of stupidity. – JoePythonKing Mar 01 '19 at 15:56
  • You'll find that a little tricky … `class X(slice): pass` results in `TypeError: type 'slice' is not an acceptable base type`. – Zero Piraeus Mar 01 '19 at 16:01
  • If it were possbile, you'd define an `__iter__` method that returned `iter(range(self.start, self.stop, self.step))` – but the point is moot given my previous comment. – Zero Piraeus Mar 01 '19 at 16:08
  • 1
    You say ... 'A slice isn't an iterable. It doesn't contain elements, ...' The documentation says ... '...Return a slice object representing the set of indices ...', now, 'the set of indices' - aren't they 'elements'? Has 'elements' got some more specific definition? – JoePythonKing Mar 01 '19 at 18:04
  • In this context, "the set of indices" aren't elements, no. They're "where the elements you'll get are, in whatever you apply this slice to". Also, note that the word used is "representing", not "containing". It might help to think of a slice as being analagous to a function signature. It's not a thing, it's a description. – Zero Piraeus Mar 01 '19 at 18:17
  • 1
    Sadly, this does only work for slices where you explicitly give all three values. For `s = slice(2)`, the command `range(s.start, s.stop, s.step)` gives an error instead of leading to the equivalent of `range(2)`. – jochen Apr 04 '20 at 11:31
  • Sorry, gotta ding it since `range(s.start, s.stop, s.step)` doesn't work when s.start or s.step are None. – Ken Seehart Aug 25 '20 at 13:03
  • Can also do `range(*s.indices(s.stop))`. – bryant1410 Jul 21 '21 at 17:26
3

Convert the slice to a range, then you can iterate the range.

range(10**10)[slice]
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Ken Seehart
  • 382
  • 3
  • 9
  • 1
    Incidentally, why is the stop argument required for range? Why not default to infinity? Seems like `range()` would be a nice idiom for the range of nonnegative integers. – Ken Seehart Aug 25 '20 at 12:46
  • When I did that, I just got the slice back - but doing `list(range(1000))[slicer]` worked to give me the values. (Python 3). – John C Aug 19 '21 at 18:07
  • list(range(1000)) is sub-optimal because it has to fill memory, which is a problem if you want it effectively unbounded. – Ken Seehart Oct 18 '21 at 04:58
  • "When I did that, I just got the slice back" No, you get a range back that behaves like the slice. The difference is that you can iterate the range. You cannot iterate the slice. See the original post. ``` – Ken Seehart Oct 18 '21 at 05:03
  • Hmm... you're right. I must have typo'ed or something, because I thought I did that exact same thing, where `type(foo)` was a slice. This time it was type range, and iterated fine without the list. Wups. – John C Oct 18 '21 at 13:46
1

The question is more general than the answer. Slice can take floating point numbers as a step where range can only take integers. So more generally:

test=slice(0,1,0.01)
testlist=[x*test.step for x in range(0,int((test.stop-test.start)/test.step+1))]

You may get rounding errors, where alternative's are discussed here

dubc
  • 41
  • 5
0

You need to get the indeces from slice object to iterate.

Here is the example:

for x in range(*slice.indices(max_index)):
    print(x)
shtse8
  • 1,092
  • 12
  • 20