Tal Weiss
Tal Weiss

Reputation: 8999

Single line for loop over iterator with an "if" filter?

I have a simple for loop followed by a simple if statement:

for airport in airports:
    if airport.is_important:

and I was wondering if I can write this as a single line somehow.

Yes, I can do this:

for airport in (airport for airport in airports if airport.is_important):

but it reads so silly and redundant (for airport in airport for airport in airports...).

Is there a better way?

Upvotes: 79

Views: 124558

Answers (9)

Dan Menes
Dan Menes

Reputation: 6797

While in most cases I would just prefer to put an if statement inside the loop, there are some cases where something like this can be concise and readable.

def process_important_airport(airport):
    print(f"{airport.name} is really important!")
    
[process_important_airport(airport) for airport in airports if airport.is_important]

Upvotes: 0

ChristopheD
ChristopheD

Reputation: 116157

You could do:

for airport in filter(lambda x: x.is_important, airports):
    # do stuff...

Upvotes: 47

abhisheksr01
abhisheksr01

Reputation: 21

I found the usage of the filter & lambda to be the easiest in a single line.

for filtered_airport in filter(lambda airport: airport.is_important, airports)):
    print(filtered_airport.some_property)

If you wish to return a list from output of this filter object you can do something like this.

filtered_airport_list = list(filter(lambda airport: airport.is_important, airports)))

Upvotes: 2

ShawnFumo
ShawnFumo

Reputation: 2178

Here's an alternative to some of the other filter versions:

from operator import attrgetter as attr
for airport in filter(attr('is_important'), airports):
    ...

This has the advantages of being pretty concise and also letting you use dot notation attr('first_class.is_full').

You could also put something like that (or a version using a list comprehension) into a utility function like filter_by_attr. Then you could do:

for airport in filter_by_attr(airports, 'is_important'):
    ...

I still think e-satis is right to put it in a new variable no matter the method you use, though. It is just clearer that way, especially if the use doesn't exactly match the name of the attribute in question (or the the criteria is more complex).

My only note on that would be that if you find yourself using this in several places, perhaps you should make airports a special collection with 'important_airports' being a @property which returns the filtered collection. Or some sort other abstraction to hide away the filtering (like a service call).

Upvotes: 3

user97370
user97370

Reputation:

I'd use a negative guard on the loop. It's readable, and doesn't introduce an extra level of indentation.

for airport in airports:
    if not airport.is_important: continue
    <body of loop>

Upvotes: 30

rogeriopvl
rogeriopvl

Reputation: 54066

Using list comprehension (only if airports is a list of objects):

for airport in [a for a in airports if a.is_important]:

Upvotes: -2

Bite code
Bite code

Reputation: 596773

No, there is no shorter way. Usually, you will even break it into two lines :

important_airports = (airport for airport in airports if airport.is_important)
for airport in important_airports:
    # do stuff

This is more flexible, easier to read and still don't consume much memory.

Upvotes: 95

Shane Holloway
Shane Holloway

Reputation: 7852

This is a design philosophy of python. If it takes you too many words to put it on one line, it should be broken into a few lines to help the person who comes after you. List and generator expressions are more for transforming iterables in-place -- making more readable forms of map and filter.

Upvotes: 3

fortran
fortran

Reputation: 76067

Mabe this, but it's more or less the same verbose...

import itertools

for airport in itertools.ifilter(lambda x: x.is_important, airports):
    ...

Upvotes: 4

Related Questions