[Python-ideas] Is this PEP-able? for X in ListY while conditionZ:

Shane Green shane at umbrellacode.com
Sat Jun 29 00:41:55 CEST 2013


Resending with the mailing list copied this time...



I don't think the use of "while" is appropriate.  A thing about list comprehension is that everything inside the brackets works around a single iteration; it's stateless, basically.  You cannot (cleanly) define a condition around the length of the output, for example. 

"While", on the other hand, implies repetition, but its usage is again stateless and agnostic to it's application or position within the comprehension.

[x until condition(x) for x in l if x]

Note that "until" suffers from the same implied duration issue that "while" did, but does benefit from not officially being a looping mechanicsm already.  In this approach the condition is not evaluated for values not returned,  and it is run against the result of any If/else clause that manipulates return values.

On Jun 27, 2013 3:17 AM, "Oscar Benjamin" <oscar.j.benjamin at gmail.com> wrote:
On 27 June 2013 08:28, Andrew Barnert <abarnert at yahoo.com> wrote:
> Let me try to gather together all of the possibilities that have been discussed
> in this and the two previous threads, plus a couple of obvious ones nobody's
> mentioned.

You've missed out having "else return" in comprehensions. I like this
less than a while clause but it was preferred by some as it unrolls
perfectly in the case that the intention is to break out of all loops
e.g.:

[f(x, y) for x in xs for y in ys if g(x, y) else return]

becomes

_tmplist = []
def _tmpfunc():
    for x in xs:
        for y in ys:
            if g(x, y):
                _tmplist.append(f(x, y))
            else:
                return
_tmpfunc()


> 1. Redefine comprehensions on top of generator expressions instead of defining them in terms of nested blocks.
>
>     def stop(): raise StopIteration
>
>     x = [value if pred(value) else stop() for value in iterable]

I prefer

x = [value for value in iterable if pred(value) or stop()]

so that the flow control is all on the right hand side of the "in".

>
> This would make the implementation of Python simpler.
>
> It also makes the language conceptually simpler. The subtle differences between [x for x in foo] and list(x for x in foo) are gone.
>
> And it's actually a pretty small change to the official semantics. Just replace the last two paragraphs of 6.2.4 with "The comprehension consists of a single expression followed by at least one for clause and zero or more for or if clauses. In this case, the expression and clauses are interpreted as if they were a generator expression, and the elements of the new container are those yielded by that generator expression consumed to completion." (It also makes it easier to fix the messy definition of dict comprehensions, if anyone cares.)
>
> Unlike #5, 6, 7, 8, and 10, but like #2, 3, 4, and 9, this only allows you to break out of one for clause, not any. But that's exactly the same as break only being able to break out of one for loop. Nobody complains that Python doesn't have "break 2" or "break :label", right?

I'm not sure why you expect that it would only break out of one for
clause; I expect it to break out of all of them. That's how it works
with generator expressions:

Python 2.7.3 (default, Sep 26 2012, 21:51:14)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> list(x + y for x in 'abc' for y in '123')
['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']
>>> def stop(): raise StopIteration
...
>>> list(x + y for x in 'abc' if x == 'a' or stop() for y in '123')
['a1', 'a2', 'a3']
>>> list(x + y for x in 'abc' for y in '123' if y == '1' or stop())
['a1']

It also works that way if you spell it the way that you did:

>>> list(x + y if y == '1' else stop() for x in 'abc' for y in '123')
['a1']


> 2. Just require comprehensions to handle StopIteration.
>
> The main cost and benefit are the same as #1.
>
> However, it makes the language and implementation more complex, rather than simpler.
>
> Also, the effects of this less radical change (you can no longer pass StopIteration through a comprehension) seem like they might be harder to explain to people than the more radical one.

I think that the current behaviour is harder to explain.

> And, worse, they less radical effects would probably cause more subtle bugs. Which implies a few versions where passing StopIteration through a comprehension triggers a warning.

I would be happy if it triggered a warning anyway. I can't imagine a
reasonable situation where that isn't a bug.

> 7. Add a "magic" while clause that's basically until with the opposite sense.
>
>     x = [value for value in iterable while pred(value)]
>
> This reads pretty nicely (at least in trivial comprehensions), it parallels takewhile and friends, and it matches a bunch of other languages (most of the languages where "when" means "if", "while" means this).
>
> But it has a completely different meaning from while statements, and in fact a barely-related one.
>
> In particular, it's obviously not this:
>
>     x = []
>
>     for value in iterable:
>         while pred(value):
>             x.append(value)
>
> What it actually means is:
>
>     x = []
>
>     for value in iterable:
>         if pred(value):
>             x.append(value)
>        else:
>            break
>
> Imagine trying to teach that to a novice.

I can definitely imagine teaching it to a novice. I have taught Python
to groups of students who are entirely new to programming and also to
groups with prior experience of other languages. I would not teach
list comprehensions by unrolling them unless it was a more advanced
Python programming course. To explain the list comprehension with
while clauses I imagine having the following conversation and
interactive session:

'''
Okay so a list comprehension is a way of making a new list out of an
existing list. Let's say we have a list called numbers_list like

>>> numbers_list = [1,2,3,4,5,4,3,2,1]
>>> numbers_list
[1, 2, 3, 4, 5, 4, 3, 2, 1]

Now we want to create a new list called squares_list containing the
square of each of the numbers in numbers_list. We can do this very
easily with a list comprehension and it looks like

>>> squares_list = [n ** 2 for n in numbers_list]
>>> squares_list
[1, 4, 9, 16, 25, 16, 9, 4, 1]

The list comprehension loops through all the numbers in numbers_list
and, calling the current number n, computes n squared (n ** 2). As it
does this is puts all the n squared numbers into a new list in the
same order.

We can also add an "if clause" to choose which elements from
numbers_list we will use to make the new list. To make a list that is
the square of all the numbers from numbers_list that are less than 4
we can do

>>> [n ** 2 for n in numbers_list if n < 4]
[1, 4, 9, 9, 4, 1]

Now the comprehension includes n ** 2 in the new list only if n < 4;
otherwise n is ignored and the comprehension moves on to the next
number from numbers_list.

Also if we want the list comprehension to stop looping over the
numbers in numbers_list after, for example, seeing a particular number
we can use a "while clause" instead of an "if clause". If we want the
comprehension to read numbers from numbers_list only while all of the
numbers seen are less than 4 then we could do

>>> [n ** 2 for n in numbers_list while n < 4]
[1, 4, 9]

In this case what happens is that as soon as the comprehension finds
the number 4 from numbers_list the while condition isn't true any more
so it stops reading numbers from numbers_list. This means it doesn't
find the other numbers that are also less than 4 at the end of
numbers_list (unlike the if clause).
'''

> 8. Allow else break in comp_if clauses.
>
>     x = [value for value in iterable if pred(value) else break]
>
> This one is pretty easy to define rigorously, since it maps to exactly what the while attempt maps to with a slight change to the existing rules.
>
> But to me, it makes the code a confusing mess. I'm immediately reading "iterable if pred(value) else break", and that's wrong.

You wouldn't have that confusion with "else return".


Oscar
_______________________________________________
Python-ideas mailing list
Python-ideas at python.org
http://mail.python.org/mailman/listinfo/python-ideas
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20130628/c16d9882/attachment.html>


More information about the Python-ideas mailing list