Hacker News new | ask | show | jobs
by ambicapter 3743 days ago
> planets_flat = [planet for episode in episodes.values() for planet in episode['planets']]

Can somebody explain this one to me (I understand list comprehensions)? I'm having trouble understanding how the second part uses something defined in the first part, but the first part can't stand on its own, so

> [planet for episode in episodes.values()]

returns an error.

6 comments

It's equivalent to

  planets_flat = []
  for episode in episodes.values():
    for planet in episode['planets']:
      plants_flat.append(planet)
Notice how the for loops in the comprehension goes in the same order as in the imperative code.
> Notice how the for loops in the comprehension goes in the same order as in the imperative code.

Thanks, that's a sane way to explain the order. Up to right now, it was always "the opposite of what you'd expect", which was a memory rule that always failed me.

It also helps on how ifs should be inserted, for example:

  ys = []
  for x in xs:
    if P(x):
      for y in Y(x):
        if Q(x,y):
          ys.append(y)
Becomes

  [y
     for x in xs
     if P(x)
     for y in Y(x)
     if Q(x,y)
  ]
Surely combining this many for/if's may often be the wrong idea. Just like making a depth 4 iterative loop isn't always ideal. It does make the order easier to remember though :)
Which is why you should probably avoid nesting list comprehensions. A normal for loop with a single list comprehension inside it works just as well, and is far easier to read.
It also works with the if-filtering, which usually goes in the inner most loop.

Actually I don't recall if you can put an if between two for's, like you might do in an imperative loop.

Look at the second example in https://docs.python.org/3/tutorial/datastructures.html#list-...

Once you know how it translates to regular for loops, this syntax is pretty natural. I always put each for loop on its own line, which makes things more readable.

That's admittedly a pretty confusing example. I don't recommend ever iterating over multiple lists in a single comprehension. A mental breakdown is:

For episodes in episodes

for planet in episode planets

give me planet

the binding of `planet` isn't evaluated until the end (left to right evaluation for the looping bits)

Python for comprehensions are middle-ended for some reason: part of it goes backwards and part of it goes forwards. Read it as:

    planets_flat = [planet for planet in episode['planets'] for episode in episodes.values()]
and it makes a lot more sense. In most languages with list comprehensions you'd write the thing you were iterating over first, e.g. (Scala):

    val planetsFlat = for { episode <- episodes.values; planet <- episode(planets) } yield planet
It's a list comprehension with an implicit nested loop:

    >>> [y for z in [[1,2,3],[4,5,6]] for y in z]
    [1, 2, 3, 4, 5, 6]
The error you get is because "planet" is bound by the second FOR clause, so when you leave it out, planet becomes unbound.
The term nested list comprehension is used differently here: https://docs.python.org/3/tutorial/datastructures.html#neste...

Don't know what a better term might be, but it's good to be aware of the distinction.

Good point. I fixed it.
> Can somebody explain this one to me?

Nope. Well - I could if I tried but that particular form is fairly abhorrent to me. My brain hurts just reading it so this is the stage I would find a clearer way to express the algorithm.