python list comprehension with multiple 'if's

PythonIf StatementList Comprehension

Python Problem Overview


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?

Python Solutions


Solution 1 - Python

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.

Solution 2 - Python

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')]

Solution 3 - Python

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]

Solution 4 - Python

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionStefanView Question on Stackoverflow
Solution 1 - PythonEmil VikströmView Answer on Stackoverflow
Solution 2 - PythonMartijn PietersView Answer on Stackoverflow
Solution 3 - PythonkylieCattView Answer on Stackoverflow
Solution 4 - PythonpokeView Answer on Stackoverflow