23
votes

My question is about Python List Comprehension readability. When I come across code with complex/nested list comprehensions, I find that I have to re-read them several times in order to understand the intent.

Is there an intuitive way to read aloud list comprehensions? Seems like I should start "reading" from the middle, then read the if conditions (if any), and read the expression last.

Here's how I would read the follow line of code aloud, in order to understand it:

[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]

"For each element in List x, and each element in List y, if the two elements are not the same, create a list of tuples."

Two examples that I am struggling with: How would you read the following List Comprehensions aloud?

  1. From another question in Stack Overflow: [x for b in a for x in b]

  2. Python docs has this example: [[row[i] for row in matrix] for i in range(4)]

Any suggestions or pointers for ways to read aloud list comprehensions such that the intention becomes clearer is much appreciated.

2
don't read programming code aloud, that usually confuses more than it clarifies - Lie Ryan
@LieRyan My question is really around readability and understanding others' code. I wrote "read aloud" because I didn't know how else to phrase it. - Ram Narasimhan
readability doesn't imply reading aloud; I usually try to categorize the comprehension's loop form. There are three general ways to nest comprehensions, the [... for ... in A for ... in B] pattern forms a cartesian product; the [... for ... in [... for ... in A]] forms a pipeline (as in shell pipes); and the [[... for ... in A] for ... in B] creates a multidimensional array. After categorizing the looping's general form, interpreting the expression and if-condition part becomes trivial. - Lie Ryan
Since list comprehension follows the mathematical set-builder notation, in the more simple cases it may be helpful to grasp it using this notation itself: S = {(x, y) : x ∈ {1,2,3} and y ∈ {3,1,4}, x != y}. In more complex examples it's not very helpful, though. - OfirD

2 Answers

33
votes

I usually unfold it in my mind into a generating loop, so for example

[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]

is the list comprehension for the generator

for x in [1,2,3]:
    for y in [3,1,4]:
        if x != y:
            yield (x, y)

Example #1

[x for b in a for x in b] is the comprehension for

for b in a:
    for x in b:
        yield x

Example result for a = [[1,2,3],[4,5,6]]: [1, 2, 3, 4, 5, 6]


Example #2

[[row[i] for row in matrix] for i in range(4)] (note the inner expression is another comprehension!):

for i in range(4):
    yield [row[i] for row in matrix]

which is unfolded

for i in range(4):
    l = []

    for row in matrix:
        l.append(row[i])

    yield l
4
votes

"Construct a list of X's based on Y's and Z's for which Q is true."