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