backend / python core / tricky questions / 12_comprehension_scope.md

Comprehension scope

2 min read source

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 3 — loop var doesn't leak:
[i for i in range(3)]
print(i)      # NameError: i is not defined  (in fresh scope)

# 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:

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

# 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:

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

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)].