Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Slicing behavior of python range()[:]

I came across an interesting bit of code in a QC review and was surprised by its behavior. I am curious if it is documented anywhere.

for i in range(0, my_array.max(), 3)[:]:
    # other code here

I was curious about the need for the [:] after range, so I tested it:

>>> range(0, 10, 3)
range(0, 10, 3)
>>> range(0, 10, 3)[:]
range(0, 12, 3)

The actual sequence defined by these ranges is identical, but I do not see this slicing behavior documented anywhere in the Python range documentation, so I was curious what is actually going on here.

like image 756
BTR Avatar asked Aug 20 '19 15:08

BTR


People also ask

What is range slicing in Python?

Slicing range() function in Python Depending on how many arguments the user is passing to the function, the user can decide where that series of numbers will begin and end as well as how big the difference will be between one number and the next.

Can we slice range in Python?

Python supports slice notation for any sequential data type like lists, strings, tuples, bytes, bytearrays, and ranges.

What is slice and range slice operation in Python?

The slice() method returns a portion of an iterable as an object of the slice class based on the specified range. It can be used with string, list, tuple, set, bytes, or range objects or custom class object that implements sequence methods __getitem__() and __len__() methods.

How does a slicing handle out of range indexes?

The slicing operation doesn't raise an error if both your start and stop indices are larger than the sequence length. This is in contrast to simple indexing—if you index an element that is out of bounds, Python will throw an index out of bounds error. However, with slicing it simply returns an empty sequence.


2 Answers

For a moment let's pretend that range still returned a list. Slicing the range object returns a range object which would act as if you were slicing the underlying list. Instead of doing this with a list though, the range object is able to take care of it in constant time using arithmetic.

>>> range(0, 90, 2)[10:23]
range(20, 46, 2)

>>> list(range(0, 90, 2)[10:23])
[20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44]

When you do something like:

range(0, 10, 3)[:]

Python slices it with arithmetic.

My assumption is that when determining the final element it rounds up. It tries to compute the zeroth element in the range to start with. This will be start + step * 0 = 0.

Then Python tries to get the ending element. There are (10 - 0) // 3 + 1 = 4 elements in the range, so the ending element is start + step * n_elements = 0 + 3 * 4 = 12.

like image 130
Primusa Avatar answered Sep 17 '22 13:09

Primusa


I think a few things are mixed here.

  • range produces slicing behavior, because slicing with non-default indexes makes sense:
>>> list(range(10, 20)[3:7])
[13, 14, 15, 16]
  • There is an idiom of copying a list (which is mutable) by producing a slice with all default indexes: some_list[:] is equivalent to something like [x for x in some_list].
  • There is the strange code that does [:] for the range object (or the actual list, if it's Python 2) which makes no sense. The generated range object / list is not referenced anywhere else anyway.
  • Python documentation lists slicing among "Common Sequence Operations" in a chapter named "Sequence Types — list, tuple, range" (emph. mine). So it's documented, but few people ever read it.
like image 30
9000 Avatar answered Sep 16 '22 13:09

9000