
Hi Brice, On Fri, Mar 3, 2017 at 1:14 AM, Brice PARENT <contact@brice.xyz> wrote:
A word about compatibility and understandability before: "as" is already a keyword, so it is already reserved and easy to parse. It couldn't be taken for its other uses (context managers, import statements and exceptions) as they all start with a specific and different keyword. It would have a really similar meaning, so be easy to understand. It doesn't imply any incompatibility with previous code, as current syntax would of course still be supported, and it doesn't change anything anywhere else (no change in indentation levels for example, no deprecation).
That still would make it usable only on Python 3.7+ It seem to me you ca already do that now with helper object/function: In [1]: class Loop: ...: ...: def __init__(self, iterable): ...: self._it = iter(iterable) ...: self._break = False ...: ...: def __next__(self): ...: if self._break: ...: raise StopIteration ...: return (self, next(self._it)) ...: ...: def __iter__(self): ...: return self ...: ...: def brk(self): ...: self._break = True ...: ...: In [2]: for loop,i in Loop(range(10)): ...: if i==5: ...: loop.brk() ...: print(i) 1 2 3 4 5<break>
Syntax: for element in elements as elements_loop: assert type(elements_loop) is ForLoopIterationObject
Properties and methods (non exhaustive list, really open to discussion and suggestions): for element in elements as elements_loop: elements_loop.length: int # len ? count ? elements_loop.counter: int # Add a counter0, as in Django? a counter1 ? elements_loop.completed: bool elements_loop.break() elements_loop.continue() elements_loop.skip(count=1)
I did not implement these, but they are as feasible. (break is keyword though, so you need to rename it)
Examples of every presented element (I didn't execute the code, it's just to explain the proposal):
################################################## # forloop.counter and forloop.length
for num, dog in enumerate(dogs): print(num, dog)
print("That's a total of {} dogs !".format(len(dogs)))
# would be equivalent to
for dog in dogs as dogs_loop: print(dogs_loop.counter, dog)
I find it less readable than enumerate.
print("That's a total of {} dogs !".format(dogs_loop.length))
# -> cleaner, and probably faster as it won't have to call len() or enumerate() #(but I could easily be wrong on that)
Though I can see how accumulating the length while iterating may make sens.
################################################## # forloop.length when we broke out of the list
small_and_medium_dogs_count = 0 for dog in generate_dogs_list_by_size(): if dog.size >= medium_dog_size: break
small_and_medium_dogs_count += 1
print("That's a total of {} small to medium dogs !".format( small_and_medium_dogs_count))
# would be equivalent to
for dog in generate_dogs_list_by_size() as dogs_loop: if dog.size >= medium_dog_size: break
print("That's a total of {} small to medium dogs !".format( dogs_loop.length - 1))
# -> easier to read, less temporary variables
################################################## # forloop.break(), to break out of nested loops (or explicitly out of current #loop) - a little like pep-3136's first proposal
has_dog_named_rex = False for owner in owners: for dog in dogs: if dog.name == "Rex": has_dog_named_rex = True break
if has_dog_named_rex: break
# would be equivalent to
for owner in owners as owners_loop: for dog in dogs: # syntax without "as" is off course still supported if dog.name == "Rex": owners_loop.break()
See my above proposal, you would still need to break the inner loop here as well. So i'm guessing you miss a `break` afrer owner_loop.break() ?
# -> way easier to read and understand, less temporary variables
################################################## # forloop.continue(), to call "continue" from any nested loops (or explicitly #in active loop)
has_dog_named_rex = False for owner in owners: for dog in owner.dogs: if dog.name == "Rex": has_dog_named_rex = True break
if has_dog_named_rex: continue
# do something
# would be equivalent to
for owner in owners as owners_loop: for dog in owner.dogs: if dog.name == "Rex": owners_loop.continue()
you are missing a break as well here you need to break out of owner.dogs, but fair. I think though that having to `.continue()` the outer loop before breaking the inner (you have to right ?) can make things complicated.
# do something
# -> way easier to read and understand, less temporary variables
################################################## # forloop.completed, to know if the list was entirely consumed or "break" was #called (or exception caught) - might help with the for/except/elseproposal
broken_out = False for dog in dogs: if dog.name == "Rex": broken_out = True break
if broken_out: print("We didn't consume all the dogs list")
# would be equivalent to
for dog in dogs as dogs_loop: if dog.name == "Rex": break
if not dogs_loop.completed: print("We didn't consume all the dogs list")
# -> less temporary variables, every line of code is relevant, easy to #understand
################################################## # forloop.skip # In the example, we want to skip 2 elements starting from item #2
skip = 0 for num, dog in enumerate(dogs): if skip: skip -= 1 continue
if num == 2: skip = 2
# would be equivalent to
for dog in dogs as dogs_loop: if dogs_loop.counter == 2: dogs_loop.skip(2)
# -> way easier to read and understand, less temporary variables
# Notes : # - Does a call to forloop.skip() implies a forloop.continue() call or does # the code continue its execution until the end of the loop, which will # then be skipped? Implying the .continue() call seems less ambiguous to # me. Or the method should be called skip_next_iteration, or something # like that. # - Does a call to forloop.skip(2) adds 2 to forloop.length or not? # -> kwargs may be added to allow both behaviours for both questions.
# We could allow the argument to be a function that accepts a single argument #and return a boolean, like dogs_loop.skip(lambda k: k % 3 == 0) # Execute the code on multiples of 3 only
##################################################
Thoughts : - It would allow to pass forloop.break and forloop.continue as callback to other functions. Not sure yet if it's a good or a bad thing (readability against what it could offer). - I haven't yet used much the asynchronous functionalities, so I couldn't yet think about the implications of such a new syntax to this (and what about a lazy keyword in here?) - I suppose it's a huge work to create such a syntax. And I have no idea how complicated it could be to create methods (like break() and continue()) doing what keywords were doing until now. - I'm not sure if that would make sense in list comprehensions, despite being confusing. - Would enable to support callback events like forloop.on_break. But would there be a need for that? - Would this have a major impact on the loops execution times? - would a "while condition as condition_loop:" be of any use too?
I think most of what you ask can be achieve by a Generator with some custom function, and does not require new syntax. It's good to be able to do that as an external library then you can experiment evolve it to get real world usage. And you can use it now ! Cheers, -- M