Multiple "if" s in comprehensions vs "and"

I would write [x for x in y if x >2 and x % 3 == 1]

Someone else consistently doubles up the if and writes
[x for x in y if x >2 if x % 3 == 1]

Although I breeze through reading nested for loops in comprehensions, the doubled if format doesn’t scan as well for me, which is probably because I don’t read it often enough.

How about you, what scans best? What do you encounter most often?

I even did not know that it is possible to have multiple if clauses.

Is there a reason for that? At the moment I can only imagine it can be a little bit clearer if we need to split conditions to multiple lines:

[
    x for x in y
    if x > 2
        and x % 3 == 1]

vs

[
    x for x in y
    if x > 2
    if x % 3 == 1]

…but having an additional syntax possibility just because of that is bad IMHO.

5 Likes

I was a Python programmer before comprehensions were added, could that have a bearing? But I don’t read that doubled if much?

Neither do I. :slightly_smiling_face: It was already legal in Python 2.7.

I would write the version with the and, but my preference isn’t particularly strong.

I wonder whether there is a performance difference between the two?

I think it’s purely a matter of style.

[x
    for x in y
        if x > 2
            if x % 3 == 1
]

FYI

1 Like

I recommend using and rather than multiple if-clauses.

  1. The origin of list comprehensions was from the set builder notation in mathematics where use of for a logical-and is the norm.
  2. A virtue of list comprehensions is that they can be read left to right in a way that feels like natural English (I’m not sure about other spoken languages). For example, [name.capitalize() for name in users if name.islower() and name not in the blocked_users] reads as “make a list where the name is capitalized for every name in the users list but only if the name is all lowercase and the name is not in the set of blocked users”. We read as plain English, “and” fits better help than multiple ifs.
  3. The norm in the Python world is to use and in list comprehensions rather than multiple-ifs. Many users don’t even know that the latter is possible. For code to be maximally intelligible to others, it should follow their idioms and norms.
  4. The origin of and is from propositional calculus, so it is well suited to logical manipulations. For example, applying De Morgan’s theorem to the OPs example gives, [x for x in y if not (x <= 2 or x % 3 != 1)]. Sometimes this is a simplification or improvement to readability, and sometimes not. But it is harder to do with nested ifs than with and.
  5. Lastly, I agree with @Paddy3118 that the multiple ifs don’t “scan well”.
4 Likes

Thanks for those links.
If it’s a matter of style, I see the use of and, and am more comfortable with writing and reading that. How about you, what’s your preference? :slightly_smiling_face:

Hmm, not much difference in timings from this little test using Spyder with ipython’s %timeit:

Python 3.10.5 | packaged by conda-forge | (main, Jun 14 2022, 06:57:19) [MSC v.1929 64 bit (AMD64)]
Type "copyright", "credits" or "license" for more information.

IPython 7.33.0 -- An enhanced Interactive Python.

In [1]: %timeit [x for x in range(2**16) if x % 3 == 1 and x % 5 == 1]
4.99 ms ± 26.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [2]: %timeit [x for x in range(2**16) if x % 3 == 1 if x % 5 == 1]
4.97 ms ± 13.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [2]: 

In [3]: %timeit [x for x in range(2**20) if x % 3 == 1 and x % 5 == 1]
81.2 ms ± 310 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [4]: %timeit [x for x in range(2**20) if x % 3 == 1 if x % 5 == 1]
81.5 ms ± 206 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [5]: 
1 Like