[Python-ideas] For/in/as syntax

Brice PARENT contact at brice.xyz
Fri Mar 3 04:14:31 EST 2017


Object: Creation of a ForLoopIterationObject object (name open to 
suggestions)
using a "For/in/as" syntax.

Disclaimer:
I've read PEP-3136 which was refused. The present proposal includes a 
solution
to what it tried to solve (multi-level breaking out of loops), but is a 
more
general proposal and it offers many unrelated advantages and 
simplifications.
It also gives another clean way of making a kind of for/except/else 
syntax of
another active conversation.

What it tries to solve:
During the iteration process, we often need to create temporary 
variables (like
  counters or states) or tests that don't correspond to the logic of the 
algo,
but are required to make it work. This proposal aims at removing these
unnecessary and hard to understand parts and help write simpler, 
cleaner, more
maintainable and more logical code.
So, more PEP-8 compliant code.

How does it solve it:
By instanciating a new object when starting a loop, which contains some 
data
about the iteration process and some methods to act on it. The list of
available properties and methods are yet to be defined, but here are a 
subset
of what could be really helpful.

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).

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)

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)

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)


##################################################
# 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()

# -> 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()

     # 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?

Sorry for the very long message, I hope it will get your interest. And I 
also
hope my English was clear enough.

Brice Parent


More information about the Python-ideas mailing list