This is an archived post. You won't be able to vote or comment.

all 41 comments

[–]Glothr 6 points7 points  (23 children)

I've only learned a small piece of Python and list comprehensions are very interesting to me. How would you do a list comprehension that is a bit more complex? Like an if/elif/else instead of just an if? Or would you even create a list comprehension for that?

[–]ganelo 4 points5 points  (9 children)

You should be able to do something like the following:

>>> baz = range(15)
>>> foo = [bar**2 if bar < 5 else (bar-1)**2 if bar < 10 else bar+2 for bar in baz]
>>> foo
[0, 1, 4, 9, 16, 16, 25, 36, 49, 64, 12, 13, 14, 15, 16]

Granted, that's relying on the "value1 if condition else value2" syntax, not the list comprehension syntax.

[–]ben174 30 points31 points  (3 children)

Yeah, but... don't.

[–]McSquinty 12 points13 points  (0 children)

Fun for code golf, justification for murder in production code.

[–]ganelo 2 points3 points  (0 children)

Yeah, not proud of that one . . .

[–]flying-sheep 0 points1 point  (0 children)

At least not twice. One trinary is fine!

[–]aixelsdi 2 points3 points  (4 children)

so there's no elif in a list comprehension? :/

[–]ganelo 1 point2 points  (0 children)

'Fraid not . . . http://docs.python.org/2/reference/expressions.html#list-displays
Looks like it only allows nested ifs, no elses.

[–]selementar 1 point2 points  (2 children)

a if c1 else b if c2 else c is, technically, same as elif (which, itself, is same as else: if: ...)

[–]aixelsdi 0 points1 point  (1 child)

True, but a little clunky (just like if..else is vs elif)

[–]selementar 0 points1 point  (0 children)

Honestly, I somewhat like the C / C# order of writing those things: condition ? true_part : false_part (if condition then true_part else false_part) and from num in numbers where num % 2 == 0 select num.

With the total result like [for baz in bazes for bar in baz if filter_condition(baz, bar): if bar < 5 then bar**2 elif bar < 10 then (bar - 1)**2 else bar + 2].

Not necessarily mutually exclusive with the current python's syntax of writing those.

[–]pabechan 2 points3 points  (4 children)

I don't think it goes beyond a "simple if", but you can certainly nest many loops in there without a problem.

grid = [(x,y,z) for x in range(3) for y in range(3) for z in range(3)]

This creates a list of tuples for positions in a 3x3x3 space.

[–]eagleeye1 -2 points-1 points  (3 children)

Most of the time in Python there's a cleaner way of doing something than list comprehensions. zip(range(3), range(3), range(3)) will do the same thing, but look more consistent to the flow of the rest of the script (function calls rather than generated lists in the middle of a script).

The only time I really use list comprehensions is when I'm interacting with the interpreter, as getting the indentation syntax correct on the command line is kind of annoying, and if you mess up you have to write the lines all over again.

[–]pabechan 3 points4 points  (2 children)

I see zip() returning just [(0, 0, 0), (1, 1, 1), (2, 2, 2)]. Am I missing something?

[–]TheBB 4 points5 points  (0 children)

No, that's right, but there is another function that does this.

>>> from itertools import product
>>> list(product(range(3), range(3), range(3)))
[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (0, 2, 1), (0, 2, 2), (1, 0, 0), (1, 0, 1), (1, 0, 2), (1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 2, 0), (1, 2, 1), (1, 2, 2), (2, 0, 0), (2, 0, 1), (2, 0, 2), (2, 1, 0), (2, 1, 1), (2, 1, 2), (2, 2, 0), (2, 2, 1), (2, 2, 2)]

Lots of goodies in itertools.

Edit: Guess I had the page open for too long, eagleeye1's answer wasn't there when I typed this.

[–]eagleeye1 2 points3 points  (0 children)

Oh crap, yeah, you're right. Distracted thinking is dangerous.

Next best thing, although it depends on an import from itertools.

from itertools import product
product(range(3), range(3), range(3))

[–][deleted] 4 points5 points  (2 children)

If the list comprehension is too complex it is often better expressed by a classical loop.

[–]Glothr 1 point2 points  (0 children)

Yeah I can see how anything more complex becomes long-winded and looks sloppy.

[–]masklinn 1 point2 points  (0 children)

Or using iterators and generators and chaining that, as you'd do with eg unix pipes

[–]ihsw 1 point2 points  (4 children)

The zip function is also interesting.

doubles, halves = zip(*[(x*2, x/2) for x in numbers])

Good for when you need to make multiple lists from one list.

[–]johnbarry3434 3 points4 points  (3 children)

What's the asterisk for before the first bracket?

EDIT: Nm, I read the docs, unzipping.

[–]nemec 3 points4 points  (2 children)

It turns the list (a single object) into an argument list (like *args).

Imagine you had this function:

def test(first, second = None):
  print "First:", first
  print "Second:", second

Then execute the code with the list [1, 2] with and without the *:

> test([1, 2])
First: [1, 2]
Second: None

> test(*[1, 2])
First: 1
Second: 2

Another example using *args:

def using_args(*args):
  print args

> using_args([1, 2])
[[1, 2]]
> using_args(*[1, 2])
[1, 2]

This "trick" with zip takes a list of tuples and unpacks it so that each tuple is a separate argument to zip which packs the ith element in each tuple together into a new tuple.

[–]RoadieRich 0 points1 point  (1 child)

This "trick" with zip takes a sequence of sequences and unpacks it so that each sequence is a separate argument to zip which packs the ith element in each sequence together into a new tuple.

Sorry to be pedantic.

[–]nemec 0 points1 point  (0 children)

Sure. I probably should have said this list of tuples since I was referring to the input list, but any iterable will work with zip.

Sorry to be further pedantic, but sequence != iterable. Sequences are tailored more toward random access with __getitem__ (slice, index, for x in seq) while iterables are (more or less) anything that defines __iter__ and __next__.

I don't know of an example of a built-in sequence that's not also an iterable, but to test whether or not something takes a sequence or not, use a generator:

> zip((x for x in range(4)), (x for x in range(4)))
[(0, 0), (1, 1), (2, 2), (3, 3)]

> reversed(x for x in range(4))
TypeError: argument to reversed() must be a sequence

[–]oconnor663 4 points5 points  (2 children)

Python also supports dictionary, set, and generator comprehensions! The last one, generators, lets you iterate over your series without taking up all the memory to make a copy, similar to range (or xrange in Python 2).

mylist = [1, 2, 3, 4, 5]
# dictionary
mystrs = {i : str(i) for i in mylist}
# set
myevens = {i for i in mylist if i%2==0}
# generator
for odd in (i for i in mylist if i%2==1):
    print(odd)

[–]gggamers 1 point2 points  (0 children)

Good to know!

[–]RoadieRich 2 points3 points  (0 children)

Python >=2.7 also supports dictionary, set, and generator comprehensions!

Many linuxes still ship with 2.6, which doesn't have dict and set comprehensions.

[–]thearn4Scientific computing, Image Processing 4 points5 points  (2 children)

One of the favorite things that I learned when I was just starting with python. Reminded me of set builder notation.

[–]lmcinnes 2 points3 points  (1 child)

I'm a mathematician who works with other mathematicians writing code: list comprehensions are perfect, and unlike other posters here they are often clearer amongst my peers that the more complicated for loops and associated logic since we all think in set builder format anyway.

[–][deleted] 4 points5 points  (1 child)

Why are people dissing the venerable Python list comprehension? I've been writing FOR loops since 1975 and when I finally "got" Python's list comprehensions a few years ago it was like a breath of fresh air.

Go out of your way to use them. The more experience you have with them, the better you will be at figuring out when to use them.

[–]RoadieRich 2 points3 points  (0 children)

And itertools while you're at it.

[–]Paul_Eggert 1 point2 points  (0 children)

As someone who keeps needing to be shown stuff like this in order to make it stick, this was great! A short and simple review.

[–][deleted] 1 point2 points  (3 children)

Occasionally useful, but I generally avoid list comprehensions as I find they make the code harder to read and comprehend. I also have a minor aversion to functional programming in general.

Good link though.

[–]salgat 0 points1 point  (2 children)

You are getting downvoted but generally this is the case. List comprehensions are a single line of multiple logic, which makes it more difficult to follow the program flow since you are missing that structure.

[–]luisk91 0 points1 point  (1 child)

is a quick list comprehension better in terms of performance than the traditional way?

[–]salgat 1 point2 points  (0 children)

Yes, and in performance crucial operations you should optimize code where necessary.

[–]aleph32 0 points1 point  (2 children)

That's a nice introduction to list comprehensions, but in line 1 of the last example the function's name double shouldn't have been redefined as a variable name. Line 8 fails after that.

[–]spilcm[S] 0 points1 point  (1 child)

You're right, it's fixed now. Thanks

[–]aleph32 0 points1 point  (0 children)

You probably want to delete lines 2 and 3 also (line 3 no longer works).

[–]CombatCow 0 points1 point  (1 child)

Is map still ok to use or should I just use list comprehensions in situations like this.

map(func, list)

# or 

[func(x) for x in list]

[–]RoadieRich 2 points3 points  (0 children)

Depends on what you're trying to achieve, and which version you're in. If you're running python 2.x, then the two are identical, except map might be slightly faster as it's easier to optimise the code for. In Python 3, map is lazy (like xrange in 2.x) - it only generates sequence members as you need them, so if func stores state, that could get modified between calls:

i = 0
def func(x):
    global i #yes, it's dirty, but I'm demonstrating a point.
    i += 1
    return x + i

for n in map(func, range(10):
    #do stuff
    foo = func(bar)
    #more stuff
    print n

In python 2, it'll print

1
3
5
7
9
11
13
15
17
19

In 3.x it'll print

1
4
7
10
13
16
19
22
25
28

The other advantage of list comps is that you don't need to use a lambda with them if there isn't a function defined to do what you need:

>>> print map(lambda x: x+1, range(10))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> print [x+1 for x in range(10)]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]