A "for ... if" statement

What do people think about a simple (in theory?) addition to the for loop to make the following valid Python:

for val in iterator if predicate(val):
    # do stuff with val

This would be equivalent to

for val in iterator:
    if predicate(val):
        # do stuff with val

The closest you can get currently I think is

for val in (val for val in iterator if predicate(val)):
    # do stuff with val

or

for val in filter(predicate, iterator):
    # do stuff with val

The first form feels just a little bit awkward. The second form loses its elegance if your predicate isn’t already a convenient function.

The main motivation is to save a level of indentation and be able to express intent a bit more directly.

This has been discussed many times, including these:

if-statement in for-loop

if-syntax for regular for-loops

for-loop-if like list comps have?

Syntax proposal of for…in…if in regular for loops

The benefit is negligible:

  • you save one line, and one indent level;

  • new lines are cheap, who cares about one line?

  • indents are not so cheap, but if your code has so many indents that this becomes important, you probably should refactor your code.

Often, you can’t use this, because the line length will exceed your style guide’s limit on line length, so you will need to split into two lines anyway:

Even if your style guide allows unreasonably long lines, long lines are much harder to read so this will hurt readability, not help it.

Simple cases might be okay:

for obj in seq if cond:
    ...

but more realistic examples are not so good:

for ext in ('.py', '.pyc', '.pyo') if os.path.isfile(os.path.join(path, '__init__' + ext)):
    ...

The for...if line is 91 columns. Too long! Put it inside a method of a class, and you have 99 columns. Even worse.

So my opinion is:

  • one more special case for people to learn about Python’s syntax;

  • usually hurts readability, not helps;

  • more complicated language rules, for negligible benefit.

The big difference between this proposal and list comprehensions is not the “save one line” part, but that comprehensions are expressions that can be used in other parts of code, but this is a statement.

When I have long comprehensions, I almost always split them over multiple lines, and usually at the “if”:

result = function(arg, obj,
            [long_expression for x in some_sequence
             if some_long_condition],
            key=something)

so saving a line in a loop is just not that important to me.

2 Likes

The indent cost is negligible if you do this

for val in iterator:
  if not predicate(val):
    continue
  do_something(val)
1 Like

Its half as much, but the cost to readability, greater ease of introducing and difficulty spotting bugs due to mistaken intent levels, and lack of conformance with standard Python conventions is much less negligible.

I don’t think Michel was referring to halving the indent to 2 spaces,
but to reversing the sense of the test.

# instead of this
for obj in items:
    if condition:
        block  # two indents

# reverse the test
for obj in items:
    if not condition:
        continue
    block  # one indent

Either style is of course perfectly Pythonic, and a matter of taste.

2 Likes

Ah yes, of course. My silly mistake; I should have examined it more carefully—indeed, I typically prefer that style myself, at least for for blocks longer than a few lines.

Thanks for the feedback! I think I’m going to live with doing something like:

iterator_i_want = (obj for obj in iterator_i_have if predicate(obj))
for obj in iterator_i_want:
    do_stuff(obj)

Personally, I’m a bit allergic to continues

To be more specific, that’s a generator (which is a type of iterator). And in that case, why not just

for obj in (obj for obj in iterator_i_have if predicate(obj)):
    do_stuff(obj)

Its shorter, simpler, and closer to your ideal syntax.

That’s up to you; IIRC there are some people who prefer not to use break and continue as they feel they make the control flow less predictable and harder to follow, ala goto. Personally, at least when they are used in predictable places (at the top or bottom of the loop), they don’t tend to pose a practical problem in that way. And if its between using them or multiple layers of nesting (which can lead to easy to make and hard to catch bugs, that judging by how many users on the help forum here make them, seems like a bigger concern), I’d usually take the break/continue. But I do tend to avoid them if there’s other equally readable and performant ways to structure the code.

I did propose that in the original post, and in most cases I don’t mind it. I guess my point was, in cases where that feels a bit noisy (which are the cases I wanted the new syntax for), splitting the generator expression into its own line is a reasonable enough solution that doesn’t require any changes to the language.