Why does a comprehension in a Python class body raise NameError for a class attribute?
answer
- Two adjacent lines, only one fails
- A class body is not an enclosing scope
- Only the first iterable is evaluated outside
- Module globals still resolve fine
- Fails at class-definition time, not at use
basics
~20 sThe comprehension body runs in its own implicit function scope, and a class body is not an enclosing scope a nested function can read. Class-level names are therefore invisible inside it, except in the outermost iterable.
solid answer
~50 sName resolution inside the comprehension's implicit function scope skips straight from the comprehension to the enclosing *function* or module and then to builtins; the class namespace is never consulted, exactly as it is not for any function defined in a class body. That is why `scaled = [w * factor for w in widths]` in a class body raises `NameError` for `factor` while `doubled = [w * 2 for w in widths]` works: `widths` is the outermost iterable, evaluated in the class body before the comprehension's scope is entered, so it resolves fine. The error is raised while the class body executes, so the class never gets created. Practical fixes: promote the constant to a module-level global, which the comprehension can read; build the value with a plain `for` statement, which is not a scope; or compute it after the class exists.
code
python · 13 linesclass Report:
widths = [3, 5]
doubled = [w * 2 for w in widths]
print(Report.doubled) # [6, 10]
try:
class Broken:
widths = [3, 5]
factor = 2
scaled = [w * factor for w in widths]
except NameError as exc:
print("class body:", exc)go deeper
Recall the symptom and one fix: a comprehension in a class body cannot read names defined at class level, so move the constant to module scope or use a plain for loop. You do not need the full mechanism at this level.
Explain both halves: the comprehension body is a function scope and class bodies are not in the lookup chain, while the first iterable is evaluated in the class body and therefore resolves fine. Predict correctly which of two similar lines fails.
Show that you know when it fires — at class-definition time, so the module fails to import — and pick the fix that matches intent: a module constant for a real constant, a plain loop to keep it on the class, or computing after the class exists when the value is derived per class.
Treat it as a signal about declarative class-level DSLs. If your codebase derives class attributes from other class attributes often, decide whether that belongs in a base class hook or a metaclass rather than in the class body, where scoping rules will keep surprising contributors.
## The two-line demonstration ```python class Report: widths = [3, 5] factor = 2 doubled = [w * 2 for w in widths] # fine scaled = [w * factor for w in widths] # NameError: name 'factor' is not defined ``` One line reads a class-level name and works; the next reads a class-level name and fails. Explaining the difference is the whole question. ## Why the failure happens The comprehension's body executes in an implicit function scope. When a name is looked up in a function scope, Python searches that scope, then the enclosing *function* scopes, then the module globals, then builtins. Class bodies are deliberately not part of that chain: a function defined inside a class body cannot see the class's attributes as bare names either, which is why methods reach them through `self` or `cls`. The comprehension inherits that rule because its body is, semantically, a function defined in the class body. So `factor` is searched for in the comprehension's own scope, then in any enclosing function (there is none here), then in module globals and builtins — and never in the class namespace where it actually lives. ## Why the first iterable is different The outermost iterable is not part of the comprehension's body. It is evaluated in the scope that *contains* the comprehension — here, the class body — and the resulting iterator is passed into the implicit scope. Class-level names therefore resolve normally there. That gives you a precise, testable rule: in a class body, exactly one expression in a comprehension may reference class-level names, and it is the iterable of the first `for` clause. The output expression, every `if` filter, and every later `for` clause's iterable cannot. ## When the error appears A class body executes top to bottom at class-definition time, so the `NameError` is raised while the module is being imported, not when someone later uses the class. That is helpful — it fails loudly and early — but it also means the traceback points at an import, and the class simply does not exist afterwards. A related consequence of the same scoping rule: an assignment expression (`:=`) inside a comprehension in a class body is a `SyntaxError`, because there is no scope for its target to bind into. ## The fixes, and which to prefer **Promote the value to a module-level constant.** Globals *are* in the lookup chain, so this simply works and is usually the honest answer when the value was a constant all along. ```python FACTOR = 2 class Report: widths = [3, 5] scaled = [w * FACTOR for w in widths] ``` **Use a plain `for` statement in the class body.** A `for` statement is not a scope, so its body executes directly in the class namespace and sees everything there. It costs a few lines and a stray loop variable that you may want to `del`, but it keeps the constant on the class where it belongs. **Make the first iterable carry the data.** Because the outermost iterable is evaluated in the class body, you can feed the needed values in through it. This works, but it turns a readable comprehension into a puzzle and should be a last resort rather than a party trick. **Compute after the class body.** Assign the attribute once the class object exists, or build it in `__init_subclass__` or a metaclass hook if it must be derived per subclass. This is the right shape when the derived value is genuinely about the class as a whole rather than a constant that happened to be written nearby. ## Why it is worth knowing The pattern shows up in real code wherever people build declarative class-level tables — field lists, column definitions, enum-like mappings — from other class-level data. The failure mode is jarring because the name is *right there*, three lines up, and the error says it does not exist. Candidates who can explain it are demonstrating that they understand comprehension scoping as a real function scope rather than as syntax sugar, and that they know class bodies are not closures. Candidates who cannot usually try to fix it by reordering the assignments or by adding `self`, neither of which can help. Nothing here changed in recent versions. Python 3.12's inlining of list, set and dict comprehensions was careful to preserve the observable scoping rules, so the same code fails the same way on 3.14 as it did on 3.6.
- Which part of a comprehension written in a class body can reference class-level names?Only the iterable of the first `for` clause. It is evaluated in the containing scope — the class body — before the comprehension's implicit scope is entered, so class-level names resolve there normally. The output expression, every `if` filter and every later `for` clause's iterable run inside the implicit scope and cannot see the class namespace.
- Does replacing the comprehension with a plain for loop fix it, and why?Yes. A `for` statement does not introduce a scope, so its body executes directly in the class namespace and can read every name defined above it. The cost is a few extra lines, an explicitly initialised accumulator, and a loop variable left bound in the class namespace, which you usually want to `del` so it does not become a class attribute.
- When is the NameError raised — at import or on first use of the class?At import. A class body executes top to bottom while the `class` statement runs, so the comprehension is evaluated during class creation and the `NameError` propagates out of the `class` statement itself. The class object is never bound, so the module fails to import rather than failing later at an attribute access.
saying these in an interview costs you the question
- Says a comprehension should see class attributes like any block
- Claims adding self or cls in the class body would fix it
- Thinks the outermost iterable fails for the same reason
- Blames definition order or a typo instead of scoping
- Says declaring the attribute global inside the class fixes it
- Believes the error only appears when the class is used