skip to content

What does UnboundLocalError being a subclass of NameError mean for exception handling?

level: middleimportance: should knowfreq 40%

answer

  1. One is a category, one is a diagnosis
  2. Catching the base catches both
  3. Order handlers most-specific first
  4. No search happened for the local case
  5. Usually the right handling is none

basics

~20 s

An except NameError clause catches UnboundLocalError too, since it is a subclass. Catch the subclass when you mean the narrow case: a name the compiler made local that has no value yet, not a name missing everywhere.

solid answer

~40 s

`UnboundLocalError` inherits from `NameError`, so any handler for `NameError` swallows it as well — usually by accident. The two carry different diagnoses. `NameError` means the interpreter found no binding at all: a typo, a missing import, or a name defined later at module level. `UnboundLocalError` means the compiler already decided the name is **local** to this function and the slot has no value: a scoping mistake, or a branch that skipped the assignment. Because the second is almost always a bug in your own code, the right handling is usually none at all — let it propagate and fix the binding. If you must distinguish them, order the handlers subclass-first, since a preceding `except NameError` would shadow the more specific clause.

code

python · 10 lines
python
def broken():
    value = value + 1

for call in (broken, lambda: undefined_name):
    try:
        call()
    except UnboundLocalError as exc:
        print("unbound local:", exc)
    except NameError as exc:
        print("missing name:", exc, exc.name)

go deeper

for a junior

Know that UnboundLocalError is a kind of NameError, so a handler for NameError catches it as well. Recognise which of the two a traceback is showing you and what each one usually means.

for a middle

Explain the diagnostic difference — no binding anywhere versus a local slot with no value — and the ordering rule for handler clauses. Know that the message text was rewritten in 3.11 and that the free-variable case reports as NameError.

for a senior

Argue the handling policy: this is a defect, so let it propagate and fix the binding. Be able to justify the narrow exceptions, such as a host executing untrusted snippets, and to show what the handler must do there instead of swallowing.

for a principal

Own the standard: broad name-error handlers hide defects across a codebase, and log-scraping rules written against old message text silently stop matching after an interpreter upgrade. Set the policy for what may be caught and where failures must surface.

## The hierarchy `UnboundLocalError` is defined as a direct subclass of `NameError`, which is itself a subclass of `Exception`. That single relationship drives every practical consequence: - `except NameError:` catches both. - `except UnboundLocalError:` catches only the narrow case. - Putting `except NameError` before `except UnboundLocalError` in the same `try` makes the second clause dead code, because clauses are tested in order and the first match wins. It is worth being precise about what each one asserts, because the vocabulary overlaps in a way that misleads people. **`NameError` means: I looked and there is no such binding.** Nothing in the local scope, the enclosing scopes, the module globals, or builtins provides a value for this name. The overwhelmingly common causes are a typo, a missing import, or referring to a module-level name before the line that defines it has executed. Since Python 3.10 the exception carries the offending identifier on its `name` attribute, and tracebacks may suggest a close match from the surrounding namespace. **`UnboundLocalError` means: I did not need to look, and there is no value here.** The compiler already classified the name as local to this code block because the block binds it somewhere, so no search happens at run time; the frame slot for that name is simply empty. Causes are a body that reads before it assigns, a body that meant to touch a module-level name without declaring `global`, a branch that never ran, or a `del` that left the name local but unbound. ## Why the subclassing is right Both are failures to resolve a name, so code that genuinely wants "a name went wrong" — a REPL front end, a template evaluator, a plugin loader executing user-supplied snippets — can catch the base class and be complete. Meanwhile the subclass preserves the far more useful diagnosis for anyone who cares to ask for it. That is the same design as `FileNotFoundError` under `OSError`: catch the base when you are handling a category, catch the leaf when you are handling a specific cause. ## What the exception instance carries On 3.14, a genuine `NameError` populates `.name` with the identifier that failed. An `UnboundLocalError` raised for an unbound local does **not** populate it — `.name` is `None` — so code that logs `exc.name` will print nothing useful for the local case. Log `str(exc)` instead; since 3.11 the message reads "cannot access local variable 'x' where it is not associated with a value", which names the variable directly. On releases before 3.11 the same condition read "local variable 'x' referenced before assignment", so log-scraping rules written against the old text stop matching after an upgrade. One related case surprises people: a *free* variable — a name a nested function reads from an enclosing function — that has no value raises `NameError`, not `UnboundLocalError`, with a message about a free variable in an enclosing scope. So "unbound name in a function" does not map one-to-one onto the subclass; only names classified as local to the block itself do. ## How to handle it in real code The honest answer for application code is: **do not catch it.** `UnboundLocalError` is a defect in the function that raised it, not an environmental condition a caller can recover from. Catching it converts a loud, precisely-located failure into a silent wrong result, and the usual follow-on symptom — a value quietly missing downstream — costs far more to diagnose than the original traceback. There are two defensible exceptions. The first is a boundary that executes code it did not write: a plugin host, a rules engine, a notebook-style evaluator. There, catching `NameError` (which brings the subclass along) to report a useful diagnostic to the author of the snippet is correct — but the handler reports, it does not continue as if nothing happened. The second is a broad top-level handler in a long-running worker that must not die on one bad work item; even there, the handler should log the traceback and record the item as failed rather than substitute a default. If you find yourself reaching for `except NameError` to paper over a name that is *sometimes* bound, the real fix is to bind it unconditionally before the branch — give it an explicit initial value, or restructure so the function returns from inside the branch. Making the binding total is always cheaper than making the failure catchable. ## The interview shape Expect the question in two forms. The first is a straight hierarchy check: "does `except NameError` catch it?" The second is a code-review prompt: a snippet where a bare `except NameError` wraps a block, and the interviewer wants you to notice that a scoping bug is being swallowed and to say what you would do instead.

  • A code review shows `except NameError: pass` wrapping a whole function body. What do you say?
    That it converts a defect into silent wrong behaviour. Both a typo and a scoping bug now vanish, and the function returns as if it succeeded. I would delete the handler and fix the binding, or, if the block genuinely executes untrusted code, keep the catch but make it report the failure with the traceback rather than swallow it.
  • Why does reading an unbound name from an enclosing function raise NameError rather than UnboundLocalError?
    Because the name is not local to the nested function; it is a free variable resolved through the enclosing scope's shared cell. The subclass is reserved for names the compiler classified as local to the block that raised. Python reports the free-variable case as a NameError whose message says the name is not associated with a value in the enclosing scope.
  • Should a long-running worker catch UnboundLocalError so one bad item does not kill it?
    Catch broadly at the top of the item loop if the worker must survive, but do it as `except Exception`, log the full traceback, and mark the item failed. Singling out UnboundLocalError implies it is an expected condition, and it never is — it is a bug that should be fixed rather than tolerated.

saying these in an interview costs you the question

  • Says the two exceptions are unrelated classes
  • Puts `except NameError` before the more specific clause
  • Catches it routinely to keep code running
  • Claims `exc.name` always identifies the variable
  • Thinks any unbound name in a function raises the subclass
  • Treats it as an environmental error rather than a defect

context