2

I will be stealing the form of the question there: List comprehension with condition

I have a simple list.

>>> a = [0, 1, 2]

I want to make a new list from it using a list comprehension.

>>> b = [afunction(x) for x in a]
>>> b
[12458, None, 34745]

Pretty simple, but what if I want to operate only over non-None elements? I can do this:

>>> b = [y for y in [afunction(x) for x in a] if y != None]

I would rather like to be able to do it with single list comprehension, in a single run. This, I believe iterates over the list again to filter out the Nones, and nobody really likes that.

Community
  • 1
  • 1
Utkan Gezer
  • 3,009
  • 2
  • 16
  • 29
  • 4
    You can't do that, unless you have two calls to `afunction` within the *"single run"* of the list comp (i.e. `[afunction(x) for x in a if afunction(x) is not None]`). Or iterate over a `map` (`[y for y in map(afunction, a) if y is not None]`) or generator expression, which at least avoids building the list – jonrsharpe Jun 04 '16 at 11:02
  • Mapping also iterates over the list twice, right? – Utkan Gezer Jun 04 '16 at 11:07
  • 1
    No, because it doesn't build the list then iterate over it again, it consumes the application of `afunction` one element of `a` at a time. – jonrsharpe Jun 04 '16 at 11:08
  • @jonrsharpe> The `map` approach is elegant, especially as `map` returns an iterator. Unfortunately, I find it is too complex for inclusion into code that will be used by other people. Still, it would deserve moving to an answer rather than being a comment. – spectras Jun 04 '16 at 11:08
  • 2
    @spectras what is *"complex"* about it? You could use the generator expression form instead, replacing `map(...)` with `(afunction(x) for x in a)` (note parentheses `()` not square brackets `[]`), but I don't see that as improving readability. – jonrsharpe Jun 04 '16 at 11:10
  • Alas, I found that nesting anything other than a regular function inside a list comprehension or a generator expression confuses the hell out of many devs. In the end, to be sure they don't misunderstand it and break it, I usually break it up in separate lines. – spectras Jun 04 '16 at 11:12
  • Converting the inner list comp to a gen exp is the simplest: `[y for y in (afunction(x) for x in a) if y is not None]`, but Jon's version using `map` may be slightly faster. Another option is to use `filter(lambda y: y is not None, map(afunction, a))`. That will return an iterator in Python 3, which may not be desirable. The most readable (IMHO) is to use `.append` in a traditional `for` loop, but it is less compact & _slightly_ slower than a list comp. – PM 2Ring Jun 04 '16 at 11:25
  • 2
    @spectras - It's probably not the best idea to plan around code needing to be read and understood by devs who aren't familiar with the language at hand. – TigerhawkT3 Jun 04 '16 at 11:29
  • @spectras: Any _competent_ Python dev should be able to cope with one or two levels of nesting in list comps / gen exps, although I agree it's advisable to split the parts over multiple lines. However, 3 or more levels turns a list comprehension into a list incomprehension, and splitting those is only of limited effectiveness. :) – PM 2Ring Jun 04 '16 at 11:31
  • I was not thinking of dumbing down the code, just on having one line in which I store the iterator given by map into a variable, then the list comprehension on the next line. Exactly the same code path, but doesn't seem to trip people as much for some reason. Maybe giving a name to the map helps creating a mental picture. – spectras Jun 04 '16 at 11:33

1 Answers1

4

You don't need to build the list then iterate over it again, you can consume an iterator of the application of afunction to the elements in a, either using map:

[y for y in map(afunction, a) if y is not None]

or a generator expression (note change in the type of brackets):

[y for y in (afunction(x) for x in a) if y is not None]
          # ^ here                  ^ and here

Note also that these test for None by identity, not equality, per PEP-8.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • On my previous comment I have said that map approach probably iterates over the list twice. I said that after looking for the description of `map` from Python's documentations. Apparently it was like the way I said on Python 2.x, now it is like you said in Python 3.x. Thanks. – Utkan Gezer Jun 04 '16 at 12:04
  • 2
    @ThoAppelsin yes, that was a change made in 3.0 (with the retirement of 2.x looming I'm assuming 3.x unless the OP says otherwise!) – jonrsharpe Jun 04 '16 at 12:06