[Python-ideas] Range and slice syntax

Nicholas Harrison nicholasharrison222 at gmail.com
Sun Nov 11 00:58:02 EST 2018

I'm aware that syntax for ranges and slices has been discussed a good
amount over the years, but I wanted to float an idea out there to see if it
hasn't been considered before. It's not really original. Rather, it's a
combination of a couple parts of Python, and I find it
fascinatingly-consistent with the rest of the language. This will look
similar to PEP 204, but there are some important differences and


Meet a range/slice object. Parentheses are required. (Its syntax in this
regard follows exactly the same rules as a generator expression.) I say
both range and slice because it can be used in either role. On the one
hand, it is iterable and functions exactly like range(start, stop, step) in
those contexts. On the other, it can also be passed into list indexing
exactly like slice(start, stop, step). This is a proposal that range and
slice are really the same thing, just in different contexts.

Why is it useful? I at least find its syntax to be simple, intuitive, and
concise -- more so than the range(...) or slice(...) alternatives. It's
quite obvious for an experienced Python user and just as simple to pick up
as slice notation for a beginner (since it *is* slice notation).

It condenses and clears up sometimes-cumbersome range expressions. A couple

sum(1:6) # instead of sum(range(1, 6))


for i in (1:6):


(i**2 for i in (1:6))

It also makes forming reusable slices clearer and easier:

my_slice = (:6:2) # instead of slice(None, 6, 2)

It has a couple of siblings that should be obvious (think list or set

[start:stop:step] # gives a list
{start:stop:step} # gives a set

This is similar to passing a range/slice object into the respective

[1:6] # list(1:6) or [1, 2, 3, 4, 5]
{1:6} # set(1:6) or {1, 2, 3, 4, 5}

Note that the parentheses aren't needed when it is the only argument of a
function call or is the only element within brackets or braces. It takes on
its respective roles for these bracket and brace cases, just like
comprehensions. This also gives rise to the normal slice syntax:

my_list[1:6:2] # What is inside the brackets is a slice object.
my_list[(1:6:2)] # Equivalent. The parentheses are valid but unnecessary.

So here's the part that requires a little more thought. Any of the values
may be omitted and in the slice context the behavior has no changes from
what it already does: start and stop default to the beginning or end of the
list depending on direction and the step defaults to 1. In the range
context, we simply repeat these semantics, but noting that there is no
longer a beginning or end of a list.

Step defaults to 1 (just like range or slice).
Start defaults to 0 when counting up and -1 when counting down (just like
If stop is omitted, the object will act like an itertools.count object,
counting indefinitely.

I have found infinite iteration to be a natural and oft-desired extension
to a range object, but I can understand that some may want it to remain
separate and pure within itertools. I also admit that the ability to form
an infinite list with only three characters can be a scary thought (though
we are all adults here, right? ;). Normally you have to take a couple extra

from itertools import count
# rather than just [:]

If that is the case, then raising an error when iter() is called on a
range/slice object with no stop value could be another acceptable course of
action. The syntax will still be left valid.

And that's mainly it. Slice is iterable or range is "indexable" and the
syntax can be used anywhere successive values are desired. If you want to
know what it does or how to use it in some case, just think, "what would a
slice object do?" or "what would a range object do?" or "how would I write
a generator expression/list comprehension here?".

Here are a few more examples:

for i in (:5): # 5 elements 0 to 4, i.e. range(5)


 for i in (1:): # counts up from one for as long as you want, i.e. count(1)


if i == 5: break

it = iter(:) # a convenient usage for an infinite counter


' '.join(map(str, (:5:2))) # gives '0 2 4'

[(:5), (5:10)] # list of range/slice objects
[[:5], [5:10]] # list of lists
[*(:5), *(5:10)] # uses unpacking to get flat list
[*[:5], *[5:10]] # same unpacking to get flat list

Otherwise you'd have to do:

[list(range(5)), list(range(5, 10))] # list of lists
[*range(5), *range(5, 10)] # flat list


tuple(1:6:2) # (1, 3, 5)
*(1:6:2), # same

I don't actually have experience developing the interpreter and underlying
workings of Python, so I don't know how much of a change this requires. I
thought it might be possible since the constructs already exist in the
language. They just haven't been unified yet. I also realize that there are
a few other use-cases that need to be ironed out. The syntax might also be
too minimal in some cases to be obvious. One of the trickiest things may be
what it will be called, since the current language has the two different

In the end it's just another range/slice idea, and the idea has probably
already been proposed sometime in the past few decades, but what thoughts
are there?

- Nicholas
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20181110/36ee746e/attachment-0001.html>

More information about the Python-ideas mailing list