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
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/