skip to content

Why does Python reject `nonlocal total` when no enclosing function binds `total`?

level: middleimportance: should knowfreq 30%

answer

  1. It fails before any code runs
  2. Compile-time classification, not a lookup
  3. The compiler searches enclosing function scopes
  4. Module scope does not count as enclosing
  5. Contrast with global, which may create

basics

~20 s

nonlocal is resolved while the code is compiled. The compiler searches the enclosing function scopes for an existing binding of that name and, finding none, raises SyntaxError: no binding for nonlocal 'total' found before the module runs at all.

solid answer

~40 s

`nonlocal` is a static declaration, not a runtime lookup. At compile time CPython walks the enclosing *function* scopes for a binding of the name -- an assignment, a parameter, a `for` target, an `import` or a nested `def` all count -- and if it finds none, compilation of the whole module fails with `SyntaxError: no binding for nonlocal 'total' found`. Module-level names do not count, which is also why `nonlocal` at module level is rejected. `global` is deliberately asymmetric: a module namespace is a dictionary that can gain a key at any time, so `global total` is legal even when nothing named `total` exists yet and the first assignment creates it. A function scope has no such dictionary to grow, so the target must already be visible in the source.

code

python · 12 lines
python
src = """
def outer():
    def inner():
        nonlocal missing
        missing = 1
"""

try:
    compile(src, "<demo>", "exec")
except SyntaxError as exc:
    print(type(exc).__name__, "-", exc.msg)
# SyntaxError - no binding for nonlocal 'missing' found

go deeper

for a junior

Recall that nonlocal only works when an enclosing function already has that variable, and that the usual fix is to initialise it in the outer function. The failure shows up when the file is imported, not when the function runs.

for a middle

Explain the compile-time mechanics: CPython classifies every name in a function before any code runs, and rejects a nonlocal with no enclosing binding outright. Contrast that with global, which is allowed to create the module-level name on first assignment.

for a senior

Turn the asymmetry into a design argument. nonlocal catches a mistyped state variable at import time, whereas keeping the same state in a dictionary defers the identical mistake to a rare code path in production. Know exactly what counts as an enclosing binding.

for a principal

Frame it as where errors get detected. Declarations checked while compiling move failures left, before deployment; favour patterns whose mistakes are caught by the compiler or a type checker over ones that only surface when an unusual branch executes.

### Two different moments: compiling and running Python resolves names in a function at **compile time**, not at call time. When CPython compiles a `def`, it builds a symbol table for that function: every identifier in the body is classified as local, as a free variable belonging to an enclosing function, as a module-level (global) name, or as a builtin. `global` and `nonlocal` are inputs to that classification, which is why they are called *declarations* rather than statements that do work. `nonlocal total` asks the compiler to classify `total` as belonging to an enclosing function. To do that, the compiler walks outward through the enclosing **function** scopes looking for a binding of `total`. If it finds none, there is nothing to classify the name as, and it reports `SyntaxError: no binding for nonlocal 'total' found` -- while compiling the module, before a single line of it has executed. Import the file and you never reach your code; the import itself raises. ### What counts as an enclosing binding Any binding operation in an enclosing function is enough: * an assignment, `total = 0` * a parameter, `def outer(total):` * a `for` target, an `except E as total`, a `with ... as total` * an `import total`, a nested `def total` or `class total` Notably the binding does **not** have to appear before the inner `def` in the source. The compiler analyses the entire enclosing function body before it decides anything, so a `total = 0` written five lines *below* the nested function still satisfies the declaration. What can still go wrong is the runtime order: calling the inner function before the enclosing assignment has executed fails at run time, because the variable exists as a binding slot but holds no value yet. Two bindings do *not* count. A module-level `total = 0` is not an enclosing function binding, so `nonlocal total` in a function whose only outer `total` is at module level is an error rather than a fallback -- the fix is `global total`. And `nonlocal` written at module level, where there is no enclosing function at all, is rejected with `nonlocal declaration not allowed at module level`. ### Why `global` is deliberately asymmetric `global counter` is legal in a module that has no `counter` anywhere. The reason is a genuine difference in how the two namespaces work rather than an inconsistency: * A **module namespace is a dictionary** that lives for the life of the module object and can gain a key at any time. `global counter` promises only that the name refers to that dictionary; the first assignment adds the entry, and a read before that assignment raises `NameError` at run time. The compiler has nothing to check, so it checks nothing. * A **function scope is settled when the function is compiled.** Its set of names is fixed by the source; there is no dictionary standing by that a later assignment could grow. So the target of a `nonlocal` must be visible in the source, and if it is not, the only honest answer the compiler can give is to refuse the program. The same reasoning explains why declaring one name both `global` and `nonlocal` in a single function is rejected with `SyntaxError: name 'g' is nonlocal and global`: a name gets exactly one classification per function scope, so you must say which outer namespace you mean. ### Why this is a feature, not a nuisance The check moves an entire class of mistake from run time to import time. Mistype the state variable in a closure -- `nonlocal totl` -- and the file will not import; you find out the moment the test suite starts, on any code path, whether or not the inner function is ever called. Contrast the pre-3.0 idiom of keeping the same state in a one-element list or a dict: `state["totl"] += 1` compiles happily and blows up only when that branch runs, possibly in production and possibly months later. That is a real argument to make in an interview: `nonlocal` is not merely nicer syntax than a mutable container, it is a stronger check. The same argument covers `global`, but in the opposite direction: because `global` can invent a name, a typo in a `global` target silently creates a second module attribute that nothing else reads. `global` buys you no protection at all. ### Fixing the error when you hit it The error message names the variable, so the fix is almost always one of three: 1. **Initialise it in the enclosing function.** Add `total = 0` in the factory. This is the intended shape of the accumulator idiom. 2. **You meant module scope.** Replace `nonlocal total` with `global total` -- and then ask whether module-level mutable state is really what you want. 3. **The nesting is not what you thought.** A method body is not nested inside the class body for this purpose, and a function defined at module level has no enclosing function at all. Move the inner function inside the function that owns the variable, or pass the value in and return the result instead of rebinding anything.

  • Must the enclosing assignment appear before the nested `def` in the source?
    No. The compiler analyses the whole enclosing function body before classifying any name, so a `total = 0` written after the nested `def` still satisfies the declaration. Only runtime order can still bite: calling the inner function before the enclosing assignment has executed fails at run time, because the variable has a slot but no value yet.
  • Why is `global total` accepted when no module-level `total` exists?
    Because a module namespace is an ordinary dictionary that gains keys at runtime. `global total` promises only that the name refers to that dictionary; the first assignment creates the entry, and a read before it raises `NameError`. Nothing needs to exist at compile time, which is exactly why a `global` declaration can install a module attribute from inside a function.
  • Can one name be declared both `global` and `nonlocal` in the same function?
    No. The two name different outer namespaces, so the combination is contradictory and CPython rejects it while compiling with `SyntaxError: name 'g' is nonlocal and global`. A name gets exactly one classification per function scope, so you have to choose which namespace you mean.

saying these in an interview costs you the question

  • Thinks the error appears only when the function is called
  • Says `nonlocal` falls back to the module namespace
  • Believes the enclosing assignment must precede the nested `def`
  • Expects a NameError rather than a SyntaxError
  • Assumes `global` also requires the name to exist first

context