[Python-ideas] Range and slice syntax

Nicholas Harrison nicholasharrison222 at gmail.com
Mon Nov 12 11:31:45 EST 2018


For sake of completeness, here is another possible problem I've found with
it.

I was afraid of making something context-dependent, and therefore breaking
its consistency. Here is the use of slices in the current language that
breaks my rules:

my_array[:,2] # valid syntax, though I've typically only seen it used in
numpy

This is interpreted as a tuple of a slice object and an integer. But with
the new syntax, the slice has to be surrounded by parentheses if not the
sole element in brackets:

my_array[(:),2]

This is a sticking point and would destroy backwards compatibility. I
realize that the context-dependence is due to the behavior of the current
slice syntax. For example,

my_array[(:,2)]

is invalid even though it looks like a tuple of a slice object and an
integer.

So I'm actually OK with parentheses not being mandatory in a slicing role,
since that is already correct syntax in the current language. This might be
a reconciliation. That would mean parentheses are not required when it is
the sole argument to a function or when it is used inside indexing.

my_array[:,2] # fine

my_indexing = (:), 2 # parentheses required

my_array[((:), 2)] # same here, but no need to do this

This actually makes building up index expressions easier and gives a
possible solution to the recent slice literals discussion.
Maybe this should be the only context when parentheses aren't required, so
you know you're dealing with an object inside of a function call.

sum((1:6))


Anyways, just some more thoughts.

On Sat, Nov 10, 2018 at 10:58 PM Nicholas Harrison <
nicholasharrison222 at gmail.com> wrote:

> 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
> clarifications.
>
> (start:stop:step)
>
>
> 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 examples:
>
>
> sum(1:6) # instead of sum(range(1, 6))
>
> list(1:6)
>
>
> for i in (1:6):
>
> print(i**2)
>
>
> (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)
> my_list[my_slice]
>
>
> It has a couple of siblings that should be obvious (think list or set
> comprehension):
>
> [start:stop:step] # gives a list
> {start:stop:step} # gives a set
>
>
> This is similar to passing a range/slice object into the respective
> constructor:
>
>
> [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
> slice).
> 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
> keystrokes:
>
> from itertools import count
> list(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)
>
> print(i**2)
>
>
>  for i in (1:): # counts up from one for as long as you want, i.e. count(1)
>
> print(i**2)
>
> if i == 5: break
>
> it = iter(:) # a convenient usage for an infinite counter
>
> next(it)
>
>
> ' '.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
>
>
> Tuples:
>
> 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
> terms.
>
> 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/20181112/980621ec/attachment-0001.html>


More information about the Python-ideas mailing list