DEV Community

Cover image for Python by Structure: List Comprehensions and Their Hidden Complexity
Aaron Rose
Aaron Rose

Posted on

Python by Structure: List Comprehensions and Their Hidden Complexity

Timothy was refactoring some data processing code when he hit a comprehension he couldn't understand. "Margaret, someone wrote this comprehension and I can't figure out what it does. It's all on one line and my brain just... stops."

Margaret looked at the code:

result = [item for sublist in data for item in sublist if item > 0 if item % 2 == 0]
Enter fullscreen mode Exit fullscreen mode

"Ah," Margaret said. "That's a comprehension that's crossed the line from clever to confusing. Let me show you what's actually happening here."

The Problem: Comprehensions Gone Wrong

"Comprehensions are powerful," Margaret explained, "but they can become write-only code. When you nest loops and stack filters, the execution order isn't what most people expect."

Timothy nodded. "I thought comprehensions were supposed to make code more readable. This is the opposite."

"Exactly. Let's start simple and build up to this monster. First, a basic comprehension:"

numbers = [1, 2, 3, 4, 5]
squares = [n * n for n in numbers]
Enter fullscreen mode Exit fullscreen mode

Tree View:

numbers = [1, 2, 3, 4, 5]
squares =
    Comprehension: n * n
        For n in numbers
Enter fullscreen mode Exit fullscreen mode

English View:

Set numbers to [1, 2, 3, 4, 5].
Set squares to:
  List comprehension: n * n
    For each n in numbers
Enter fullscreen mode Exit fullscreen mode

"This is clear," Margaret said. "You're creating a new list by squaring each number. The structure is simple: for each n in numbers, compute n * n."

Adding Filters

"Now let's add a filter:"

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_squares = [n * n for n in numbers if n % 2 == 0]
Enter fullscreen mode Exit fullscreen mode

Tree View:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_squares =
    Comprehension: n * n
        For n in numbers
            If n % 2 == 0
Enter fullscreen mode Exit fullscreen mode

English View:

Set numbers to [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
Set even_squares to:
  List comprehension: n * n
    For each n in numbers
      If n % 2 == 0
Enter fullscreen mode Exit fullscreen mode

Timothy traced through it. "So it iterates through numbers, keeps only the even ones with the if filter, then squares them?"

"Right. The filter comes after the for clause. Look at the structure - the If is indented under the For, showing it filters which items get processed."

The result:

[4, 16, 36, 64, 100]
Enter fullscreen mode Exit fullscreen mode

Multiple Filters

Margaret showed him something that surprised many developers:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = [n for n in numbers if n > 3 if n % 2 == 0]
Enter fullscreen mode Exit fullscreen mode

Tree View:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result =
    Comprehension: n
        For n in numbers
            If n > 3
            If n % 2 == 0
Enter fullscreen mode Exit fullscreen mode

English View:

Set numbers to [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
Set result to:
  List comprehension: n
    For each n in numbers
      If n > 3
      If n % 2 == 0
Enter fullscreen mode Exit fullscreen mode

"Wait," Timothy said. "Two if statements in a row? Does that mean OR or AND?"

"That's the confusion point. Look at the structure - both If clauses are at the same indentation level under the For. Multiple if clauses are combined with AND logic. This is equivalent to if n > 3 and n % 2 == 0. Both conditions must be true."

The result:

[4, 6, 8, 10]
Enter fullscreen mode Exit fullscreen mode

"So you're filtering for numbers greater than 3, AND even? Why not just write if n > 3 and n % 2 == 0?"

"Exactly my point," Margaret said. "Multiple if clauses are legal, but they're harder to read than a single if with and. The structure hides the logic."

Nested Loops

"Now here's where it gets tricky," Margaret said. "Nested loops in comprehensions."

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [item for row in matrix for item in row]
Enter fullscreen mode Exit fullscreen mode

Tree View:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat =
    Comprehension: item
        For row in matrix
        For item in row
Enter fullscreen mode Exit fullscreen mode

English View:

Set matrix to [[1, 2, 3], [4, 5, 6], [7, 8, 9]].
Set flat to:
  List comprehension: item
    For each row in matrix
    For each item in row
Enter fullscreen mode Exit fullscreen mode

Timothy stared at it. "So... you iterate row in matrix, then item in row?"

"Yes. Look at the structure - both For clauses are at the same indentation level, showing they're sequential. The order in the comprehension matches the order you'd write the loops."

She showed him the equivalent loop:

flat = []
for row in matrix:
    for item in row:
        flat.append(item)
Enter fullscreen mode Exit fullscreen mode

"See? The comprehension reads left-to-right in the same order as the nested loops. The outer loop comes first, the inner loop comes second."

The result:

[1, 2, 3, 4, 5, 6, 7, 8, 9]
Enter fullscreen mode Exit fullscreen mode

Combining Nested Loops and Filters

"Now we can understand that monster comprehension from the beginning," Margaret said.

data = [[1, -2, 3], [4, -5, 6], [7, 8, -9]]
result = [item for sublist in data for item in sublist if item > 0 if item % 2 == 0]
Enter fullscreen mode Exit fullscreen mode

Tree View:

data = [[1, -2, 3], [4, -5, 6], [7, 8, -9]]
result =
    Comprehension: item
        For sublist in data
        For item in sublist
            If item > 0
            If item % 2 == 0
Enter fullscreen mode Exit fullscreen mode

English View:

Set data to [[1, -2, 3], [4, -5, 6], [7, 8, -9]].
Set result to:
  List comprehension: item
    For each sublist in data
    For each item in sublist
      If item > 0
      If item % 2 == 0
Enter fullscreen mode Exit fullscreen mode

Margaret broke it down: "Look at the structure - the two For clauses are sequential, then the two If clauses are indented under them. This is equivalent to:"

result = []
for sublist in data:           # First For
    for item in sublist:       # Second For
        if item > 0:           # First If
            if item % 2 == 0:  # Second If
                result.append(item)
Enter fullscreen mode Exit fullscreen mode

Timothy worked through it step by step:

[4, 6, 8]
Enter fullscreen mode Exit fullscreen mode

"So it flattens the nested lists, keeps only positive numbers, then keeps only even numbers from those?"

"Exactly. But the comprehension version packs all that logic into one dense line. The structure is there, but it's hard to see without the tool breaking it down."

When Comprehensions Become Too Complex

"So when should I stop using comprehensions?" Timothy asked.

Margaret showed him the decision tree:

"Use comprehensions when:

  • The logic fits comfortably on one line
  • The transformation is simple and obvious
  • You have at most one level of nesting
  • Filters are straightforward

Avoid comprehensions when:

  • You need nested loops with complex conditions
  • The logic requires multiple steps
  • Reading it aloud doesn't make immediate sense
  • You're tempted to add comments explaining it"

She rewrote the complex comprehension more clearly:

# More readable version
result = []
for sublist in data:
    for item in sublist:
        if item > 0 and item % 2 == 0:
            result.append(item)
Enter fullscreen mode Exit fullscreen mode

"Or even better, break it into steps:"

# Most readable version
flat = [item for sublist in data for item in sublist]
positive = [item for item in flat if item > 0]
even = [item for item in positive if item % 2 == 0]
Enter fullscreen mode Exit fullscreen mode

Timothy looked relieved. "So it's okay to use multiple simple comprehensions instead of one complex one?"

"Absolutely. Readability beats cleverness every time."

Conditional Expressions in Comprehensions

"There's one more pattern that confuses people," Margaret said. "The conditional expression."

numbers = [1, 2, 3, 4, 5]
result = [n if n % 2 == 0 else -n for n in numbers]
Enter fullscreen mode Exit fullscreen mode

Tree View:

numbers = [1, 2, 3, 4, 5]
result =
    Comprehension: n if n % 2 == 0 else -n
        For n in numbers
Enter fullscreen mode Exit fullscreen mode

English View:

Set numbers to [1, 2, 3, 4, 5].
Set result to:
  List comprehension: n if n % 2 == 0 else -n
    For each n in numbers
Enter fullscreen mode Exit fullscreen mode

"This is different from a filter," Margaret explained. "Look at the structure - the if/else is part of the comprehension expression itself, not a separate filter clause. It transforms every item - even numbers stay positive, odd numbers become negative."

The result:

[-1, 2, -3, 4, -5]
Enter fullscreen mode Exit fullscreen mode

"Compare that to a filter:"

result = [n for n in numbers if n % 2 == 0]  # Only keeps even numbers
Enter fullscreen mode Exit fullscreen mode

"The filter version only keeps even numbers. The conditional expression version processes all numbers but transforms them differently."

Timothy nodded. "So if/else in the expression transforms items, if as a separate clause filters them?"

"Exactly. The position and structure matter."

Dict and Set Comprehensions

"Comprehensions aren't just for lists," Margaret added.

# Dict comprehension
squares_dict = {n: n * n for n in range(1, 6)}

# Set comprehension
unique_lengths = {len(word) for word in ['cat', 'dog', 'bird', 'cat']}
Enter fullscreen mode Exit fullscreen mode

Tree View:

squares_dict =
    Comprehension: n: n * n
        For n in range(1, 6)
unique_lengths =
    Comprehension: len(word)
        For word in ['cat', 'dog', 'bird', 'cat']
Enter fullscreen mode Exit fullscreen mode

English View:

Set squares_dict to:
  Dict comprehension: n: n * n
    For each n in range(1, 6)
Set unique_lengths to:
  Set comprehension: len(word)
    For each word in ['cat', 'dog', 'bird', 'cat']
Enter fullscreen mode Exit fullscreen mode

"Same structure, different types. The English View tells you whether it's a List, Dict, or Set comprehension based on the syntax."

Results:

{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
{3, 4}
Enter fullscreen mode Exit fullscreen mode

The Readability Test

Margaret pulled the lesson together. "The structure of comprehensions is elegant, but that elegance can hide complexity. Before you write a comprehension, ask yourself: can someone else read this and immediately understand what it does?"

Timothy looked at his original confusing comprehension. "So the rule is: if I need to use the Structure Viewer to understand it, it's too complex?"

"That's a good rule. Comprehensions should make code more readable, not less. The moment you sacrifice clarity for brevity, you've gone too far."

"So I should favor simple comprehensions and break complex ones into steps?"

"Exactly. The structure shows you what's happening, but if the structure requires multiple nested levels to parse, split it up. Your future self will thank you."


Analyze Python structure yourself: Download the Python Structure Viewer - a free tool that shows code structure in tree and plain English views. Works offline, no installation required.


Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.

Top comments (0)