[Python-ideas] For/in/as syntax

Matthias Bussonnier bussonniermatthias at gmail.com
Fri Mar 3 11:21:17 EST 2017


Hi Brice,

On Fri, Mar 3, 2017 at 1:14 AM, Brice PARENT <contact at 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


More information about the Python-ideas mailing list