
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

On Sun, Nov 11, 2018 at 4:59 PM Nicholas Harrison <nicholasharrison222@gmail.com> wrote:
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
Be careful of this last one. If you omit the step, it looks like this: {start:stop} which is a dictionary display. ChrisA

On Sun, Nov 11, 2018 at 6:00 AM Chris Angelico <rosuav@gmail.com> wrote:
Be careful of this last one. If you omit the step, it looks like this:
{start:stop}
which is a dictionary display.
The parenthesis could always be required for this new syntax. In [*1*]: {'a':1} Out[*1*]: {'a': 1} In [*2*]: {('a':1)} File "<ipython-input-2-373a3153a222>", line 1 {('a':1)} ^ SyntaxError: invalid syntax -- Juancarlo *Añez*

On Sun, Nov 11, 2018 at 04:34:16PM +0000, Juancarlo Añez wrote:
On Sun, Nov 11, 2018 at 6:00 AM Chris Angelico <rosuav@gmail.com> wrote:
Be careful of this last one. If you omit the step, it looks like this:
{start:stop}
which is a dictionary display.
The parenthesis could always be required for this new syntax.
Under the proposed syntax {(start:stop)} would be a set with a single item, a slice object. If slice objects were hashable, that would be legal. The OP's proposal is for {start:stop} to be equivalent to set(range(start, stop)) so they will be very different things. -- Steve

That's a good point. It might be better to disallow the list and set versions all together. To get a list or set you would instead have to explicitly unpack a range/slice object: [*(:5)] # [:5] no longer allowed {*(1:6)} # {1:6} is a dict That would also solve the misstep of the three-character infinite list. On Sat, Nov 10, 2018 at 11:00 PM Chris Angelico <rosuav@gmail.com> wrote:
On Sun, Nov 11, 2018 at 4:59 PM Nicholas Harrison <nicholasharrison222@gmail.com> wrote:
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
Be careful of this last one. If you omit the step, it looks like this:
{start:stop}
which is a dictionary display.
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Sat, Nov 10, 2018 at 10:58:02PM -0700, Nicholas Harrison wrote: [...]
(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.
Ranges and slices are conceptually different things. Even if they have similar features, they are very different: - range is a lazy sequence, which produces its values on demand; - it supports (all? most?) of the Sequence ABC, including membership testing and len(); - but its values are intentionally limited to integers; - slice objects, on the other hand, are an abstraction referring to a context-sensitive sequence of abstract indices; - those indices can be anything you like: py> s = slice("Surprise!", range(1, 100, 3), slice(None)) py> s.start 'Surprise!' py> s.stop range(1, 100, 3) py> s.step slice(None, None, None) - they don't support membership testing, len() or other Sequence operations; - most importantly, because they are context-sensitive, we don't even know how many indexes are included in a slice until we know what we're slicing. That last item is why slice objects have an indices() method that takes a mandatory length parameter. If slices were limited to single integer indices, then there would be an argument that they are redundant and we could use range objects in their place; but they aren't. [...]
Why is it useful? I at least find its syntax to be simple, intuitive, and concise -- more so than the range(...) or slice(...) alternatives.
Concise, I will grant, but *intuitive*? I have never forgot the first time I saw Python code, and after being told over and over again how "intuitive" it was I was utterly confused by these mysterious list[:] and list[1:] and similar expressions. I had no idea what they were or what they were supposed to do. I didn't even have a name I could put to them. At least range(x) was something I could *name* and ask sensible questions about. I didn't even have a name for this strange square-bracket and colon syntax, and no context for understanding what it did. There's surely few things in Python more cryptic than mylist = mylist[:] until you've learned what slicing does and how it operates. Your proposal has the same disadvantages: it is cryptic punctuation that is meaningless until the reader has learned what it means, without even an obvious name they can refer to. Don't get me wrong: slice syntax is great, *once you have learned it*. But it is a million miles from intuitive. If this proposal is a winner, it won't be because it will make Python easier for beginners. [...]
sum(1:6) # instead of sum(range(1, 6))
That looks like you tried to take a slice of a sequence called "sum" but messed up the brackets, using round instead of square.
list(1:6)
Same.
for i in (1:6):
Looks like a tuple done wrong. I think this is not an improvement, unless you're trying to minimize the number of characters in an expression.
It also makes forming reusable slices clearer and easier:
my_slice = (:6:2) # instead of slice(None, 6, 2)
"Easier" in the sense of "fewer characters to type", but "clearer"? I don't think so. [...]
So here's the part that requires a little more thought.
Are you saying that so far you haven't put any thought into this proposal? *wink* (You don't have to answer this part, it was just my feeble attempt at humour.) -- Steve

Overall, I agree with you. It is more intuitive to an experienced Python user, and not so helpful to beginners. It decreases the ability to read out code like English sentences and makes it harder to know what to search for online. So it boosts facility after you know the language, but not when starting out. On Sun, Nov 11, 2018 at 2:35 AM Steven D'Aprano <steve@pearwood.info> wrote:
On Sat, Nov 10, 2018 at 10:58:02PM -0700, Nicholas Harrison wrote:
[...]
(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.
Ranges and slices are conceptually different things. Even if they have similar features, they are very different:
- range is a lazy sequence, which produces its values on demand;
- it supports (all? most?) of the Sequence ABC, including membership testing and len();
- but its values are intentionally limited to integers;
- slice objects, on the other hand, are an abstraction referring to a context-sensitive sequence of abstract indices;
- those indices can be anything you like:
py> s = slice("Surprise!", range(1, 100, 3), slice(None)) py> s.start 'Surprise!' py> s.stop range(1, 100, 3) py> s.step slice(None, None, None)
- they don't support membership testing, len() or other Sequence operations;
- most importantly, because they are context-sensitive, we don't even know how many indexes are included in a slice until we know what we're slicing.
That last item is why slice objects have an indices() method that takes a mandatory length parameter.
If slices were limited to single integer indices, then there would be an argument that they are redundant and we could use range objects in their place; but they aren't.
[...]
Why is it useful? I at least find its syntax to be simple, intuitive, and concise -- more so than the range(...) or slice(...) alternatives.
Concise, I will grant, but *intuitive*?
I have never forgot the first time I saw Python code, and after being told over and over again how "intuitive" it was I was utterly confused by these mysterious list[:] and list[1:] and similar expressions. I had no idea what they were or what they were supposed to do. I didn't even have a name I could put to them.
At least range(x) was something I could *name* and ask sensible questions about. I didn't even have a name for this strange square-bracket and colon syntax, and no context for understanding what it did. There's surely few things in Python more cryptic than
mylist = mylist[:]
until you've learned what slicing does and how it operates.
Your proposal has the same disadvantages: it is cryptic punctuation that is meaningless until the reader has learned what it means, without even an obvious name they can refer to.
Don't get me wrong: slice syntax is great, *once you have learned it*. But it is a million miles from intuitive. If this proposal is a winner, it won't be because it will make Python easier for beginners.
[...]
sum(1:6) # instead of sum(range(1, 6))
That looks like you tried to take a slice of a sequence called "sum" but messed up the brackets, using round instead of square.
list(1:6)
Same.
for i in (1:6):
Looks like a tuple done wrong.
I think this is not an improvement, unless you're trying to minimize the number of characters in an expression.
It also makes forming reusable slices clearer and easier:
my_slice = (:6:2) # instead of slice(None, 6, 2)
"Easier" in the sense of "fewer characters to type", but "clearer"? I don't think so.
[...]
So here's the part that requires a little more thought.
Are you saying that so far you haven't put any thought into this proposal?
*wink*
(You don't have to answer this part, it was just my feeble attempt at humour.)
-- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

I'm wondering how your examples would go with from funcoperators import infix (https://pypi.org/project/funcoperators/) sum(1:6) # instead of sum(range(1, 6))
sum(1 /exclusive/ 6) list(1:6)
list(1 /exclusive/ 6) set(1 /exclusive/ 1) Note that you can pick another name. Note that you can pick another function : @infix def inclusive (a, b): return range(a, b+1) sum(1 /inclusive/ 6) for i in (1:6):
print(i**2)
for i in 1 /exclusive/ 6: print(i**2) (i**2 for i in (1:6))
(i ** 2 for i in 1 /exclusive/ 6) It also makes forming reusable slices clearer and easier:
my_slice = (:6:2) # instead of slice(None, 6, 2) my_list[my_slice]
I don't have exact equivalent here, I would create a function or explicitly say slice(0, 6, 2) 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}
As mentioned before {1:6} is a dict. Here are a few more examples:
for i in (:5): # 5 elements 0 to 4, i.e. range(5)
print(i**2)
Everybody knows i in range(5).
for i in (1:): # counts up from one for as long as you want, i.e. count(1)
Well, count(1) is nice and people can google it.

On Sun, Nov 11, 2018 at 6:59 AM Nicholas Harrison <nicholasharrison222@gmail.com> wrote:
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.
Just to point out, with slices it's a bit more complicated than that currently. The start, stop and step values each default to None. When slice-indexing built-in and (all? probably, not sure) standard-library types, None values for start and stop are interpreted consistently with what you described as defaults. A None value for step is interpreted as either 1 or -1, depending on the comparison of start and step, and accounting for None values in either of them too. ------ In real life I've found a use for non-integer slice objects, and been happy that Python allowed me to treat the slice as a purely syntactic construct whose semantics (outside builtins) are not fixed. My case was an interface to an external sparse time-series store, and it was easy to make the objects indexable with [datetime1 : datetime2 : timedelta], with None's treated right etc. (The primary intended use was in a REPL in a data-science context, so if your first thought was a doubt about whether that syntax is neat or abusive, please compare it to numpy or pandas idioms, not to collection classes you use in server or application code.) If this had not been syntactically possible, it would not have been a great pain to have to work around it, but now it's existing code and I can imagine other existing projects adapting the slice syntax to their own needs. At first blush, it seems like your proposal would give slices enough compulsory semantics to break some of such existing code - maybe even numpy itself. (FWIW, I've also occasionally had a need for non-integer ranges, and chafed at having to implement or install them. I've also missed hashable slices in real life, because functools.lru_cache.) ------ (Note I'm just a random person commenting on the mailing list, not anybody with any authority or influence.) I find this recurring idea of unifying slices and ranges seductive. But it would take a lot more shaking-out to make sure the range semantics can be vague-ified enough that they don't break non-integer slice usage. Also, I could imagine some disagreements about exactly how much non-standard slice usage should be protected from breakage. Someone could make the argument that _some_ objects as slice parameters are just abuse and no sane person should have used them in the first place. ("Really, slicing with [int : [[sys], ...] : __import__]? We need to take care to not break THAT too?")

That's true. I should clarify what I was thinking a bit more. Maybe it's better to say that the new syntax creates a slice object: (::) # this creates slice(None, None, None) It accepts any object into its arguments and they default to None when they are left off. This can be passed into list indexing and used as a slice. The new addition is that slice is now iterable: iter(slice(None, None, None)) # becomes valid Only when this is called (implicitly or explicitly) do checks for valid objects and bounds occur. From my experience using slices, this is how they work in that context too. my_slice = slice('what?') # slice(None, 'what?', None) my_list[my_slice] # TypeError: slice indices must be integers or None or have an __index__ method # similarly iter(my_slice) # TypeError: slice indices must be integers or None or have an __index__ method I still may not understand slices well enough though. On Sun, Nov 11, 2018 at 5:13 AM Vladimir Filipović <hemflit@gmail.com> wrote:
On Sun, Nov 11, 2018 at 6:59 AM Nicholas Harrison <nicholasharrison222@gmail.com> wrote:
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.
Just to point out, with slices it's a bit more complicated than that currently.
The start, stop and step values each default to None.
When slice-indexing built-in and (all? probably, not sure) standard-library types, None values for start and stop are interpreted consistently with what you described as defaults. A None value for step is interpreted as either 1 or -1, depending on the comparison of start and step, and accounting for None values in either of them too.
------
In real life I've found a use for non-integer slice objects, and been happy that Python allowed me to treat the slice as a purely syntactic construct whose semantics (outside builtins) are not fixed.
My case was an interface to an external sparse time-series store, and it was easy to make the objects indexable with [datetime1 : datetime2 : timedelta], with None's treated right etc.
(The primary intended use was in a REPL in a data-science context, so if your first thought was a doubt about whether that syntax is neat or abusive, please compare it to numpy or pandas idioms, not to collection classes you use in server or application code.)
If this had not been syntactically possible, it would not have been a great pain to have to work around it, but now it's existing code and I can imagine other existing projects adapting the slice syntax to their own needs. At first blush, it seems like your proposal would give slices enough compulsory semantics to break some of such existing code - maybe even numpy itself.
(FWIW, I've also occasionally had a need for non-integer ranges, and chafed at having to implement or install them. I've also missed hashable slices in real life, because functools.lru_cache.)
------
(Note I'm just a random person commenting on the mailing list, not anybody with any authority or influence.)
I find this recurring idea of unifying slices and ranges seductive. But it would take a lot more shaking-out to make sure the range semantics can be vague-ified enough that they don't break non-integer slice usage.
Also, I could imagine some disagreements about exactly how much non-standard slice usage should be protected from breakage. Someone could make the argument that _some_ objects as slice parameters are just abuse and no sane person should have used them in the first place. ("Really, slicing with [int : [[sys], ...] : __import__]? We need to take care to not break THAT too?") _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

I mostly like the abstraction being proposed, but the syntactical edge cases like `[::3]` (infinite list crashes) and {4:10} (a dict not a slice/range set) tip the balance against it for me. Saying to add some various stars and parens in various not-really-obvious places just makes the proposal way too much special casing. Moreover, we can get what we want without new syntax. Or at least the bulk of it. Both Pandas and NumPy offer special accessors for slices that look the way you'd like:
import pandas as pd import numpy as np I = pd.IndexSlice J = np.s_ I[4:10:3] slice(4, 10, 3) J[4:10:3] slice(4, 10, 3)
These are incredibly simple classes, but they are worth including because many programmers will forget how to write their own. You don't get your range-like behavior with those, but it's easy to construct. I'm having a think-o. I think it should be possible to make a RangeSlice class that will act like an enhanced version of pd.IndexSlice, but my try was wrong. But simpler:
def R(sl): ... return range(sl.start, sl.stop, sl.step or sys.maxsize) ... for i in R(I[4:10:3]): ... print(i) ... 4 7
Someone should figure out how to make that simply `RS[4:10:3]` that will act both ways. :-) On Mon, Nov 12, 2018 at 10:44 AM Nicholas Harrison < nicholasharrison222@gmail.com> wrote:
That's true. I should clarify what I was thinking a bit more. Maybe it's better to say that the new syntax creates a slice object:
(::) # this creates slice(None, None, None)
It accepts any object into its arguments and they default to None when they are left off. This can be passed into list indexing and used as a slice. The new addition is that slice is now iterable:
iter(slice(None, None, None)) # becomes valid
Only when this is called (implicitly or explicitly) do checks for valid objects and bounds occur. From my experience using slices, this is how they work in that context too.
my_slice = slice('what?') # slice(None, 'what?', None)
my_list[my_slice] # TypeError: slice indices must be integers or None or have an __index__ method
# similarly
iter(my_slice) # TypeError: slice indices must be integers or None or have an __index__ method
I still may not understand slices well enough though.
On Sun, Nov 11, 2018 at 5:13 AM Vladimir Filipović <hemflit@gmail.com> wrote:
On Sun, Nov 11, 2018 at 6:59 AM Nicholas Harrison <nicholasharrison222@gmail.com> wrote:
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.
Just to point out, with slices it's a bit more complicated than that currently.
The start, stop and step values each default to None.
When slice-indexing built-in and (all? probably, not sure) standard-library types, None values for start and stop are interpreted consistently with what you described as defaults. A None value for step is interpreted as either 1 or -1, depending on the comparison of start and step, and accounting for None values in either of them too.
------
In real life I've found a use for non-integer slice objects, and been happy that Python allowed me to treat the slice as a purely syntactic construct whose semantics (outside builtins) are not fixed.
My case was an interface to an external sparse time-series store, and it was easy to make the objects indexable with [datetime1 : datetime2 : timedelta], with None's treated right etc.
(The primary intended use was in a REPL in a data-science context, so if your first thought was a doubt about whether that syntax is neat or abusive, please compare it to numpy or pandas idioms, not to collection classes you use in server or application code.)
If this had not been syntactically possible, it would not have been a great pain to have to work around it, but now it's existing code and I can imagine other existing projects adapting the slice syntax to their own needs. At first blush, it seems like your proposal would give slices enough compulsory semantics to break some of such existing code - maybe even numpy itself.
(FWIW, I've also occasionally had a need for non-integer ranges, and chafed at having to implement or install them. I've also missed hashable slices in real life, because functools.lru_cache.)
------
(Note I'm just a random person commenting on the mailing list, not anybody with any authority or influence.)
I find this recurring idea of unifying slices and ranges seductive. But it would take a lot more shaking-out to make sure the range semantics can be vague-ified enough that they don't break non-integer slice usage.
Also, I could imagine some disagreements about exactly how much non-standard slice usage should be protected from breakage. Someone could make the argument that _some_ objects as slice parameters are just abuse and no sane person should have used them in the first place. ("Really, slicing with [int : [[sys], ...] : __import__]? We need to take care to not break THAT too?") _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

On Mon, Nov 12, 2018 at 11:23:21AM -0500, David Mertz wrote:
import pandas as pd import numpy as np I = pd.IndexSlice J = np.s_ I[4:10:3] slice(4, 10, 3)
I'm not entirely sure that I like the idea of conflating slice *constructor* with slice *usage*. Slice objects are objects, like any other, and I'm not convinced that overloading slice syntax to create slice objects is a good design. I'm pretty sure it would have confused the hell out of me as a beginner to learn that mylist[1::2] took a slice and [1::2] make a slice. But at least numpy and pandas has the virtual of needing a prefix to make it work.
You don't get your range-like behavior with those, but it's easy to construct. I'm having a think-o. I think it should be possible to make a RangeSlice class that will act like an enhanced version of pd.IndexSlice, but my try was wrong.
Just because we can do something, doesn't mean we should. -- Steve

On Mon, Nov 12, 2018 at 4:43 PM Nicholas Harrison <nicholasharrison222@gmail.com> wrote:
Only when this is called (implicitly or explicitly) do checks for valid objects and bounds occur. From my experience using slices, this is how they work in that context too.
On reconsideration, I've found one more argument in favour of (at least this aspect of?) the proposal: the slice.indices method, which takes a sequence's length and returns an iterable (range) of all indices of such a sequence that would be "selected" by the slice. Not sure if it's supposed to be documented. So there is definitely precedent for "though slices in general are primarily a syntactic construct and new container-like classes can choose any semantics for indexing with them, the semantics specifically in the context of sequences have a bit of a privileged place in the language with concrete expectations, including strictly integer (or None) attributes".

That is not what slice.indices does. Per help(slice.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 handling of normal slices. Essentially, it returns (S.start, len, S.step), with start and stop adjusted to prevent out-of-bounds indices. On Tue, Nov 13, 2018, 12:50 PM Vladimir Filipović <hemflit@gmail.com wrote:
On Mon, Nov 12, 2018 at 4:43 PM Nicholas Harrison <nicholasharrison222@gmail.com> wrote:
Only when this is called (implicitly or explicitly) do checks for valid objects and bounds occur. From my experience using slices, this is how they work in that context too.
On reconsideration, I've found one more argument in favour of (at least this aspect of?) the proposal: the slice.indices method, which takes a sequence's length and returns an iterable (range) of all indices of such a sequence that would be "selected" by the slice. Not sure if it's supposed to be documented.
So there is definitely precedent for "though slices in general are primarily a syntactic construct and new container-like classes can choose any semantics for indexing with them, the semantics specifically in the context of sequences have a bit of a privileged place in the language with concrete expectations, including strictly integer (or None) attributes". _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Wed, Nov 14, 2018 at 5:14 AM David Allemang <allemang.d@gmail.com> wrote:
That is not what slice.indices does. Per help(slice.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 handling of normal slices.
Essentially, it returns (S.start, len, S.step), with start and stop adjusted to prevent out-of-bounds indices.
And to handle negative indexing.
slice(1,-1).indices(100) (1, 99, 1)
A range from 1 to -1 doesn't make sense (or rather, it's an empty range), but a slice from 1 to -1 will exclude the first and last of any sequence. ChrisA

Interesting. I haven't looked at that package before. It looks like it would work well for that. On Sun, Nov 11, 2018 at 4:48 AM Robert Vanden Eynde <robertve92@gmail.com> wrote:
I'm wondering how your examples would go with from funcoperators import infix (https://pypi.org/project/funcoperators/)
sum(1:6) # instead of sum(range(1, 6))
sum(1 /exclusive/ 6)
list(1:6)
list(1 /exclusive/ 6) set(1 /exclusive/ 1)
Note that you can pick another name. Note that you can pick another function :
@infix def inclusive (a, b): return range(a, b+1)
sum(1 /inclusive/ 6)
for i in (1:6):
print(i**2)
for i in 1 /exclusive/ 6: print(i**2)
(i**2 for i in (1:6))
(i ** 2 for i in 1 /exclusive/ 6)
It also makes forming reusable slices clearer and easier:
my_slice = (:6:2) # instead of slice(None, 6, 2) my_list[my_slice]
I don't have exact equivalent here, I would create a function or explicitly say slice(0, 6, 2)
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}
As mentioned before {1:6} is a dict.
Here are a few more examples:
for i in (:5): # 5 elements 0 to 4, i.e. range(5)
print(i**2)
Everybody knows i in range(5).
for i in (1:): # counts up from one for as long as you want, i.e. count(1)
Well, count(1) is nice and people can google it.

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@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
participants (8)
-
Chris Angelico
-
David Allemang
-
David Mertz
-
Juancarlo Añez
-
Nicholas Harrison
-
Robert Vanden Eynde
-
Steven D'Aprano
-
Vladimir Filipović