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

Oscar Benjamin oscar.j.benjamin at gmail.com
Mon Jul 1 13:57:00 CEST 2013


On 30 June 2013 00:51, Nick Coghlan <ncoghlan at gmail.com> wrote:
>    [x for x in iterable; break if x is None]
>    [x for x in data if x; break if x is None]
>
> One nice advantage of that notation is that:
>
> 1. The statement after the ";" is exactly the statement that would
> appear in the expanded loop
> 2. It can be combined unambiguously with a filtering clause
> 3. It clearly disallows its use with nested loops in the comprehension

It has the significant disadvantage that Steven pointed out which is
that it doesn't read very well. The most important aspect of a
comprehension is its comprehensibility. Consider getting the prime
numbers less than 100:

primes100 = {p for p in primes(); break if p >= 100}

You need to invert the if condition to understand which primes are in
the resulting set. With for/while it reads properly and the condition
at the right expresses a true property of the elements in the
resulting set:

primes100 = {p for p in primes() while p < 100}

At the moment the obvious way to get the prime numbers less than 100
would be to do something like:

from math import ceil, sqrt

def isprime(N):
    return N > 1 and all(N % n for n in range(2, ceil(sqrt(N))))

primes100 = [p for p in range(1, 100) if isprime(p)]

However this is a suboptimal algorithm. At the point when we want to
determine if the number N is prime we have already found all the
primes less than N. We only need to check modulo division against
those but this construction doesn't give us an easy way to do that.

It's better to have a primes() generator that can keep track of this
information:

from itertools import count

def primes():
    primes_seen = []
    for n in count(2):
        if all(n % p for p in primes_seen):
            yield n
            primes_seen.append(n)

This algorithm is actually even poorer as it doesn't stop at sqrt(n).
We can fix that with takewhile:

from itertools import count, takewhile

def primes():
    primes_seen = []
    for n in count(2):
        if all(n % p for p in takewhile(lambda p: p**2 < n, primes_seen)):
            yield n
            primes_seen.append(n)

primes100 = {p for p in takewhile(lambda p: p < 100, primes()}

Using for/while this becomes significantly clearer (in my opinion):

from itertools import count

def primes():
    primes_seen = []
    for n in count(2):
        if all(n % p for p in primes_seen while p**2 <= n):
            yield n
            primes_seen.append(n)

primes100 = {p for p in primes() while p < 100}

The main objection to for/while seems to be that it doesn't unroll in
the same way as current comprehensions. I think that for/while is just
as useful for an ordinary for loop as it is for a comprehension. In C
you can easily add anything to the termination condition for a loop
e.g.:

for (i=0; i<N && i && p[i-1]<100; ++i)
    p[i] = next_prime();

But in Python having multiple termination conditions (or having any
termination with an inifinite iterator) means using a break or
takewhile/lambda.

for n, p in enumerate(primes()):
    if p > 100:
        break
    print('%sth prime is %s' % (n, p))

or perhaps

for n, p in enumerate(takewhile(lambda p: p < 100, primes())):
    print('%sth prime is %s' % (n, p))

or even worse

for n, p in enumerate(takewhile((100).__gt__, primes())):
    print('%sth prime is %s' % (n, p))

I think that it would be better if this could be spelled as

for n, p in enumerate(primes()) while p < 100:
    print('%sth prime is %s' % (n, p))

If that were the case then a for/while comprehension could unroll into
a for/while loop just as with current comprehensions:

result = [x for y in stuff while z]

becomes:

result = []
for y in stuff while z:
    result.append(x)


Oscar


More information about the Python-ideas mailing list