Backend / Python core / Tricky questions / 12_comprehension_scope.md

Comprehension scope

Updated 2 min read source
On this page5
  1. The gotcha
  2. Minimal repro
  3. Why it happens
  4. How to avoid
  5. Interview angle

Comprehension scope

The gotcha

In Python 3, comprehensions (list, set, dict, generator) have their own scope — the loop variable doesn’t leak. In Python 2, list comprehensions did leak. This breaks naive expectations either way.

Minimal repro

python
# Python 3 — loop var doesn't leak:
[i for i in range(3)]
# NameError: i is not defined  (in fresh scope)
print(i)

# But classes inside comprehensions break:
class Outer:
    x = 1
    items = [x for _ in range(3)]
    # NameError: name 'x' is not defined

The classic class-body trap: comprehensions can’t see class-level names because comprehensions execute in a function-like scope, and that scope can see enclosing function/module names but not the surrounding class body.

Why it happens

Comprehensions in Python 3 are implemented as anonymous nested functions. The loop variable lives inside that function. This:

  • Prevents leaking (good)
  • Means the comprehension is a closure over the enclosing scope (good for normal use)
  • But class bodies are not a normal enclosing scope for nested function lookup — class scope is opt-in only via explicit references like ClassName.attr

How to avoid

For the class-body case, capture the class-level name as a default argument or refer through the class explicitly:

python
class Outer:
    x = 1
    # capture via default arg
    items = [(lambda x=x: x)() for _ in range(3)]

# Or define after the class:
class Outer:
    x = 1
Outer.items = [Outer.x for _ in range(3)]

For loop-variable leakage in Python 2-era code, just rewrite. Python 3 comprehensions are fine.

Don’t use the same name for the comprehension variable and a useful outer variable — i is tempting but easy to confuse:

python
i = "important value"
# in Python 3, outer `i` survives — but reads badly
[i for i in range(3)]

Interview angle

“What’s the difference between comprehension scope in Python 2 vs Python 3?” Or the class-body trap: predict the error in class C: x = 1; ys = [x for _ in range(3)].