[Python-ideas] For/in/as syntax
Matt Gilson
matt at getpattern.com
Fri Mar 3 11:32:34 EST 2017
Thanks for the idea and prior research.
I'm not convinced that this warrants new syntax. Most of what you propose
(skipping, counting, exposing a length if available, tracking if completed)
could be solved already by creating your own wrapper around an iterable:
elements_loop = ForLoopIterationObject(elements)
for element in elements_loop:
...
Some of your proposal simply can't be done perfectly (e.g. length) since
iterables can have infinite length. There is `__length_hint__` IIRC which
might (or might not) give a rough estimate.
You also couldn't do `elements_loop.continue()` or `elements_loop.break()`
in a python-based wrapper without special interpreter magic (at least now
as far as I can imagine...). I realize that is the point of this proposal
as it would allow breaking/continuing nested loops, however, that proposal
was already rejected because Guido didn't think that the use-cases were
compelling enough to add new syntax/complexity to the python language and
most of the time you can address that case by adding a function here or
there. In this proposal, you're adding significantly _more_ complexity
than was proposed in PEP-3136. Also, you're creating an object for every
loop -- and that object needs to do stuff when iterating (e.g. increment a
counter). It's very unlikely that this could be anything but a performance
drain compared to existing solutions (only use `enumerate` when you need
it). I suppose that the interpreter could be smart enough to only create a
ForLoopIterationObject when it actually needs it (e.g. when there is an
`as` statement), but that shifts a lot more complexity onto the language
implementors -- again for perhaps little benefit.
On Fri, Mar 3, 2017 at 1:14 AM, Brice PARENT <contact at brice.xyz> wrote:
> 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
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
--
[image: pattern-sig.png]
Matt Gilson // SOFTWARE ENGINEER
E: matt at getpattern.com // P: 603.892.7736
We’re looking for beta testers. Go here
<https://www.getpattern.com/meetpattern> to sign up!
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20170303/17f0d71a/attachment-0001.html>
More information about the Python-ideas
mailing list