[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