python list comprehension with multiple 'if's

Question:

We all know python’s

[f(x) for x in y if g(x)]

syntax.

However the
AST representation of list comprehension has room for more than one ‘if’ expression:

comprehension = (expr target, expr iter, expr* ifs)

Can somebody give me an example of python code that would produce an AST with more than one ‘if’ expression?

Asked By: Stefan

||

Answers:

Just stack them after one another:

[i for i in range(100) if i > 10 if i < 50]

Produces the integers between 11 and 49, inclusive.

Answered By: Emil Vikström

The grammar allows for multiple if statements because you can mix them between the for loops:

[j for i in range(100) if i > 10 for j in range(i) if j < 20]

The comprehension components should be viewed as nested statements, the above translates to:

lst = []
for i in range(100):
    if i > 10:
        for j in range(i):
            if j < 20:
                lst.append(j)

This also means that you can use multiple if statements without for loops in between:

[i for i in range(100) if i > 10 if i < 20]

Although non-sensical (just combine those using and or with chained operators), it does translate to a legal nested set of statements still:

lst = []
for i in range(100):
    if i > 10:
        if i < 20:
            lst.append(i)

The grammar and parser do not specifically disallow such usage, in the same way that Python doesn’t disallow you to nest if statements.

Note that PEP 202 – List Comprehensions (the original proposal document that added this feature to the language) actually includes a double-if comprehension in the examples section:

>>> print [(i, f) for i in nums for f in fruit if f[0] == "P" if i%2 == 1]
[(1, 'Peaches'), (1, 'Pears'), (3, 'Peaches'), (3, 'Pears')]
Answered By: Martijn Pieters

The language reference gives a better idea about this:

list_comprehension  ::=  expression list_for
list_for            ::=  "for" target_list "in" old_expression_list [list_iter]
list_iter           ::=  list_for | list_if
list_if             ::=  "if" old_expression [list_iter]

As you can see the list comprehension is defined with an optional list_iter at the end—a single list_iter. Now this list_iter can either be another for-part of the list comprehension or an if-condition. The if-condition itself again ends with another optional list_iter. This is essential to make it possible to chain multiple for-parts with optional if-conditions in the same list comprehension. The fact that you could also construct an .. if X if Y if Z part for the list_iter is just a side effect.

So, while the possibility to chain multiple if-conditions alone is not needed, it allows the whole grammar to be defined that way.

Answered By: poke

Using the built in all() allows you to place multiple Boolean expressions or functions in an iterable and stick in your comprehension. I think it’s a pretty under used built in and it keeps readability high.

>>> [x for x in range(20) if all([1 < x < 10, not x & 1])]
[2, 4, 6, 8]

Or

>>> [x for x in range(20) if all([foo(x), bar(x)])]

the any() built in also would work well here if only one condition needed to be satisfied:

>>> [x for x in range(20) if any([1 < x < 10, not x & 1])]
[0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18]
Answered By: kylieCatt