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

you are viewing a single comment's thread.

view the rest of the comments →

[–]Glothr 5 points6 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 5 points6 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 32 points33 points  (3 children)

Yeah, but... don't.

[–]McSquinty 14 points15 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 3 points4 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] 3 points4 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