# Scope and namespaces

Source: https://codewiki.com/python/scope-namespaces/

> - **what**: Scope controls which names code can use directly; a namespace maps those names to objects.
> - **trap**: One binding operation in a function usually makes that name local throughout the body, so an earlier read won't fall back to a same-named global and can raise `UnboundLocalError`.
> - **fix**: Identify who owns the binding, then choose parameters and return values, `nonlocal`, or `global`; don't add `global` merely to silence an error.

## What it is and why it exists

Scope describes where a name is visible in source code. A namespace holds bindings from names to objects. They answer different questions: scope tells you which name you may write here, while a namespace tells you which object that name currently denotes. Calling both a "place where variables live" blurs name lookup with object lifetime.

Python names aren't boxes that contain objects. `rate = 0.2` establishes a name binding from `rate` to a float in the current namespace, and a later assignment can point the same name at another object. Several names can also refer to one object, so a name's scope and the object's lifetime are separate concerns.

Scopes let modules and functions reuse ordinary names without inventing a globally unique label for every temporary value. A function call gets its own local bindings, a nested function can read lexically enclosing bindings, and a module provides shared global bindings. Built-in names are the last fallback, which is why `len` and `ValueError` work without imports.

You meet name binding in function parameters, assignments, imports, loop targets, pattern matching, and exception handlers. Moving an assignment into a function, generating callbacks in a loop, or changing a module constant into local configuration can all alter name resolution. Once you know the scope rules, you can derive these effects from the source instead of guessing at runtime.

## How it works

Python resolves ordinary names from lexical nesting: where a function is defined in the source, not which function happens to call it. LEGB is a useful lookup path: Local, Enclosing, Global, Built-in. It explains reads inside functions, but class blocks, comprehensions, and annotation scopes have extra rules that don't fit into four letters.

| Level | Name | Where bindings come from | If absent |
| --- | --- | --- | --- |
| L | Local scope | Parameters and binding operations in the current function | Continue to enclosing scopes |
| E | Enclosing scope | Functions that lexically contain the current function | Search outward one function at a time |
| G | Global scope | The module that defines the current function | Continue to the built-in namespace |
| B | Built-in scope | The `builtins` module | Raise `NameError` |

Before lookup happens, the compiler scans the whole function block and classifies names as local, free, or global. Any binding operation inside the function usually makes its target local to that entire block, even when the assignment follows a read or sits in a branch that never runs. The cause of `UnboundLocalError` is therefore often another assignment elsewhere in the function, not the line that failed.

Binding operations include more than `=`. Parameters, `def`, `class`, `import`, `for` targets, `with ... as`, `except ... as`, capture patterns, and assignment expressions all bind names; even `del`, which unbinds at runtime, affects compile-time classification. Attribute assignment such as `order.total = 10` and item assignment such as `items[0] = 10` mutate objects and don't bind `order` or `items` in the current scope.

An ordinary read selects the nearest visible binding in the current environment. A local name can shadow a same-named enclosing, global, or built-in name, but it doesn't delete those outer bindings. Once execution leaves the local block, the outer name still has its original binding.

### `global` and `nonlocal`

`global name` tells the compiler that uses and assignments of `name` in this block target the module's global namespace. It doesn't mean "global across every module," nor does it create storage shared across processes or threads. If the name doesn't exist yet, a later assignment creates it in the module where the current function was defined.

`nonlocal name` selects the nearest enclosing function scope where that name is already bound. It can't target module scope or create a missing outer binding; without a valid target, compilation raises `SyntaxError`. An inner function needs no declaration to read a free variable. It needs `nonlocal` only when it rebinds the enclosing name.

| Need | Preferred form | Why |
| --- | --- | --- |
| Read module configuration | Read the name directly | Reads don't need `global` |
| Change data supplied by the caller | Parameter plus return value | Ownership and data flow stay visible |
| Update state owned by one outer call | `nonlocal` | State remains in that lexical enclosing scope |
| Update state genuinely owned by the module | `global` | The module namespace is changed explicitly |

Most application functions are clearer with parameters and return values because calls expose the flow of state. `global` and `nonlocal` aren't forbidden, but they must match the actual owner. A fix that widens a value's lifetime or sharing boundary often creates a subtler bug than the error it removed.

### Special boundaries

Executing a class body creates a class namespace, which becomes the class's attribute dictionary. Ordinary names bound in that body aren't enclosing function bindings for methods, so a method uses `self.rate`, `type(self).rate`, or `Pricing.rate`, not bare `rate`. An unbound ordinary name in a class body falls back to the global namespace, another difference from an unbound local inside a function.

List, set, and dictionary comprehensions don't leak their iteration variable into the containing scope. Generator expressions also have their own execution scope. They can still read visible outer names, but code in a class-body comprehension can't treat an ordinary class attribute as a lexically enclosing variable; annotation scopes in Python 3.14 are a specific exception.

## Examples

These three examples cover LEGB reads, compile-time classification, and namespace inspection. The output comes from running each file with local `python3`; the samples use semantics shared by local Python 3.12 and the target Python 3.14.

### Following LEGB lookup

The inner `render()` function reads one name from each of the four levels. Each name is bound at only one level, so the output exposes the lookup result directly.

<!-- quick -->

```python
# file: legb_lookup.py
tax_rate = 0.20


def print_report():
    department = "returns"

    def render(order_ids):
        heading = "pending"
        print("local:", heading)
        print("enclosing:", department)
        print("global:", tax_rate)
        print("built-in:", len(order_ids))

    render([101, 102, 103])


print_report()
```

```text
local: pending
enclosing: returns
global: 0.2
built-in: 3
```


<!-- /quick -->

Lookup happens independently for each name; a function doesn't choose one namespace for every read. `heading` is local, `department` comes from the lexical outer function, `tax_rate` comes from the module, and `len` is found in the built-in namespace. A same-named binding in a nearer scope would shadow the outer one.

The caller doesn't change this path. If another function binds `department = "support"` and then calls `render()`, `render()` still reads the binding from the particular `print_report()` call where it was defined. Python is lexically scoped, not dynamically scoped.

### Comparing local, `global`, and `nonlocal`

`broken_price()` deliberately assigns to `discount` after reading it. Catching the exception lets the program continue and compare all three rebinding cases in one runnable output.

```python
# file: rebinding.py
discount = 10
calls = 0


def broken_price():
    try:
        print(discount)
    except UnboundLocalError as error:
        print(type(error).__name__)
    discount = 20
    return discount


def record_call():
    global calls
    calls += 1


def make_budget(limit):
    remaining = limit

    def spend(amount):
        nonlocal remaining
        remaining -= amount
        return remaining

    return spend


print("local:", broken_price())
record_call()
print("global:", calls)
spend = make_budget(50)
print("nonlocal:", spend(8), spend(7))
```

```text
UnboundLocalError
local: 20
global: 1
nonlocal: 42 35
```

The assignment in `broken_price()` makes `discount` local throughout the function, so the earlier `print(discount)` doesn't fall back to global `10`. The handler runs, then the local binding becomes `20`. This call leaves the module's `discount` unchanged.

`record_call()` explicitly updates a module binding, while `spend()` updates a binding owned by one `make_budget()` call. Calling `make_budget(50)` again creates an independent `remaining`. Parameters and return values are usually a better fit when state shouldn't persist across calls.

### Reading namespace views

`globals()` and `locals()` expose the current namespaces for inspection. Real namespaces contain implementation and environment names, so this example selects stable keys instead of dumping whole dictionaries.

```python
# file: namespace_views.py
REGION = "eu"


class Shipping:
    unit = "kg"
    visible_names = sorted(
        name for name in locals() if not name.startswith("__")
    )


def summarize(order_id):
    subtotal = 25
    names = locals()
    print("function:", sorted(names))
    print("values:", names["order_id"], names["subtotal"])


print("module:", globals()["REGION"])
print("class:", Shipping.visible_names)
summarize("A-17")
```

```text
module: eu
class: ['unit']
function: ['order_id', 'subtotal']
values: A-17 25
```

During class-body execution, `locals()` exposes the mapping that will be passed to the metaclass constructor, so `unit` later becomes accessible as `Shipping.unit`. The function mapping contains parameters and locals bound so far. At module level, `locals()` and `globals()` refer to the same namespace; optimized function scopes behave differently.

Python 3.14 specifies that every `locals()` call in an optimized scope such as a function, generator, or coroutine returns a fresh dictionary of current bindings. Mutating that dictionary doesn't write back to real locals, and later local assignments don't change an earlier dictionary. Treat `locals()` as a diagnostic snapshot there, not an assignment interface.

## Pitfalls

### Reading a same-named global before assignment

> **Pitfall:** A `count += 1` near the end of a function makes `count` local throughout that function. An earlier `print(count)` therefore raises `UnboundLocalError` instead of reading the global binding.

**Fix:** decide who owns the state first. Prefer passing the value in and returning its replacement; when the owner really is the module or an enclosing function, use `global` or `nonlocal` respectively and put the declaration before the first use.

### Using `global` to hide an ownership mistake

> **Pitfall:** Adding `global` as a quick response to `UnboundLocalError` can turn request, user, or object state into module-wide state. A one-call test passes, while repeated or concurrent calls interfere with one another.

**Fix:** write down the state's lifetime and sharing boundary. Keep request state in parameters, return values, or instances; use `global` only when the module truly owns the value, and test two callers with interleaved operations.

### Shadowing built-in names

> **Pitfall:** Bindings such as `list = records`, `id = order.id`, and `sum = 0` shadow their built-in counterparts. A later `list(...)`, `id(...)`, or `sum(...)` may fail far from the binding that caused the problem.

**Fix:** choose domain names such as `records`, `order_id`, and `total`. Inspect local and global bindings while diagnosing the problem; don't make `builtins.list` a permanent workaround for ambiguous naming.

### Treating a class attribute as a method's enclosing local

> **Pitfall:** A class-body binding such as `rate = 0.2` doesn't make bare `rate` in a method resolve to the class attribute. Ordinary name lookup in the method skips the class namespace and continues through the module and built-ins.

**Fix:** write instance policy as `self.rate` and class-level policy as `type(self).rate` or an explicit class name. Attribute access also makes inheritance and overrides follow the interface instead of depending on an accidental global.

### Mutating function variables through `locals()`

> **Pitfall:** Generated code sometimes writes `locals()[field] = value` to create local variables dynamically. In an optimized scope on Python 3.14, this only changes the returned dictionary; it doesn't establish a local binding readable by a bare name.

**Fix:** put dynamic fields in an explicit dictionary, dataclass, or regular object. `locals()` is suitable for diagnosis and for APIs that explicitly consume a mapping, not for writing a function's local variables.

### Confusing scope with object lifetime

> **Pitfall:** An object isn't necessarily destroyed when a name leaves scope; a container, closure, or external component may still hold a reference. Conversely, rebinding a name doesn't mutate the old object.

**Fix:** track "where is this name visible?" separately from "which references keep this object reachable?" Release files, locks, and transactions explicitly with context managers instead of relying on collection after a name goes out of scope.

<!-- deep -->

## Bindings as the compiler sees them

Local-name classification happens before a function runs. The compiler builds a symbol table for each code block and records whether a name is a parameter, local, free, nonlocal, or global. That static classification explains why runtime can't wait to see whether a branch executes before deciding to read a global.

A name used by an inner function but not bound in its own block is a free variable. The lexical outer function supplying that binding is an enclosing scope. Closures keep such bindings available after the outer call returns, but their sharing and late-binding behavior belong to the dedicated closures topic.

The standard-library `symtable` module exposes compiler symbol tables without executing the analyzed source. This program compiles a source string into symbol tables and inspects four names in the innermost function.

```python
# file: inspect_bindings.py
import symtable


SOURCE = """
fee = 2

def make_total(tax):
    def total(amount):
        subtotal = amount * (1 + tax)
        return subtotal + fee
    return total
"""


module = symtable.symtable(SOURCE, "billing.py", "exec")
factory = module.lookup("make_total").get_namespace()
total = factory.lookup("total").get_namespace()

checks = (
    ("parameter", lambda symbol: symbol.is_parameter()),
    ("local", lambda symbol: symbol.is_local()),
    ("free", lambda symbol: symbol.is_free()),
    ("global", lambda symbol: symbol.is_global()),
)

for name in sorted(total.get_identifiers()):
    symbol = total.lookup(name)
    roles = [label for label, check in checks if check(symbol)]
    print(f"{name}: {','.join(roles)}")
```

```text
amount: parameter,local
fee: global
subtotal: local
tax: free
```

Parameter `amount` is also local, `subtotal` gets a local binding from assignment, `tax` comes from the outer function, and `fee` resolves to the module. The `global` classification here describes an implicit global read. The source doesn't need `global fee` because the function never assigns to `fee`.

Symbol tables help static analysis and developer tools, but application code normally shouldn't inspect them to decide runtime behavior. If an interface only makes sense after bytecode or symbol-table analysis, ownership is too well hidden. Prefer function signatures, object attributes, and return values that expose data flow.

## What lookup errors tell you

Name-related exceptions point to different failure stages. Identify the exception first, then inspect the binding table instead of immediately adding a declaration. The name in the message is only a starting point; binding operations across the whole code block still matter.

| Symptom | What it means | Check first |
| --- | --- | --- |
| `NameError` | No visible scope supplied the name | Spelling, imports, and execution order |
| `UnboundLocalError` | The function classified the name as local, but it wasn't bound at the read | Every assignment and binding target in the function |
| `SyntaxError` at `nonlocal` | No enclosing function binding can satisfy the declaration | Whether an outer function actually binds the name |
| `AttributeError` | Name lookup found an object, but attribute lookup failed on it | The instance, class, and descriptor chain |

`AttributeError` isn't a failed LEGB lookup. In `order.total`, Python first resolves `order` by scope and then performs attribute lookup on the resulting object; diagnose those steps separately. Adding a module global named `total` won't repair a missing object attribute.

Custom code can alter the final exception, since `__getattr__()` may handle a missing attribute. Even then, separating name resolution from attribute access remains useful. Don't assume two errors follow the same lookup rules just because their messages contain the same word.

## Statements that don't create local scope

An indented Python suite isn't automatically a new scope. `if`, `for`, `while`, `with`, `try`, and `match` don't create ordinary function-like local scopes, so names bound in them are generally visible later in the same containing block. Control flow still determines whether binding occurred at runtime; an untaken branch doesn't conjure a value.

Exception handlers have one easy-to-miss detail. `except Error as error` binds `error`, but Python clears that target when the handler ends to break a reference cycle among the exception, traceback, and frame. Don't expect to read that name after the `except` suite.

Capture patterns in structural matching bind names too. When different cases leave inconsistent sets of bindings for later code, readers have a hard time proving which names exist. Construct the same kind of result inside every successful case and let later code read only that result name.

## Classes, comprehensions, and annotation scopes

A class body is executable code. Ordinary lookup there can read globals and built-ins as well as names bound earlier in the class body. Once class creation finishes, entries in the class namespace become attributes. A method function doesn't treat that namespace as an ordinary lexical enclosing scope, which is why class attributes require attribute access.

Comprehensions isolate their iteration variables and avoid Python 2-style name leakage. A comprehension executed in a class body also can't directly read ordinary class locals, because its implicit scope doesn't include the class namespace in the normal enclosing-function chain. Put the value at module level, calculate it after class creation, or use an explicit construction step.

On Python 3.14, function annotations, variable annotations, type-parameter lists, and `type` statements use annotation scopes. They mostly resemble function scopes but can access an immediately enclosing class namespace; most annotation scopes are also evaluated lazily. This is a focused rule for modern typing syntax, not evidence that a regular method can read class attributes bare.

Type parameters in annotation scopes can't be rebound with `nonlocal` from an inner scope, and expressions in annotation scopes restrict `yield`, `yield from`, `await`, and `:=`. Tools that process metaprogramming or typing constructs need to follow Python 3.14's execution model here. Ordinary application functions that don't use modern type syntax need no extra control flow for these rules.

## The contracts of `globals()` and `locals()`

`globals()` returns the namespace dictionary of the module implementing the current function. If another module imports and calls that function, `globals()` still refers to the defining module, not the caller. Mutating the dictionary changes module state and has the same broad sharing boundary as a `global` binding, so reserve it for tools that genuinely operate on namespaces.

Whether `locals()` is writable depends on the scope. Modules, classes, and non-optimized namespaces explicitly supplied to `exec()` or `eval()` expose a live mapping; optimized scopes such as functions, generators, and coroutines return independent snapshots on Python 3.14. Reading a snapshot is useful for logs and diagnosis, but its values are still references to the original objects, so mutating one of those objects can have side effects.

`exec()` and `eval()` accept global and local namespace mappings, but dynamic execution brings separate security and maintenance costs. Don't concatenate source merely to evade normal scope rules: keep structured data in dictionaries and behavior in functions or classes. Neither `eval()` nor `exec()` is a parser for untrusted input.

## Names, references, and lifetime

Scope controls where a name is visible; it doesn't promise when an object will be collected. After a function returns, callers can't directly access its ordinary local names, but a return value, closure, container, or global registry may still refer to those objects. Reachability comes from references, and exact collection timing also depends on the Python implementation and reference cycles.

Rebinding and mutation are separate operations. `items = []` points a name at a new list while other names can still refer to the old one; `items.append(value)` changes the same list seen by every reference holder. `global` and `nonlocal` control the first kind of name rebinding. They aren't permission declarations for method calls on objects.

When debugging state, draw two small tables: one maps each name to the block that binds it, and the other lists references that keep each object reachable. The first explains `NameError`, `UnboundLocalError`, and shadowing. The second explains shared mutation and object lifetime. Keeping them separate usually finds the cause faster than dumping an entire namespace.

<!-- /deep -->

[Checkpoint: python/scope-namespaces](https://codewiki.com/python/scope-namespaces/#checkpoint)

## Further reading

- [Python language reference: naming and binding](https://docs.python.org/3.14/reference/executionmodel.html#naming-and-binding)
- [Python language reference: the `global` statement](https://docs.python.org/3.14/reference/simple_stmts.html#the-global-statement)
- [Python language reference: the `nonlocal` statement](https://docs.python.org/3.14/reference/simple_stmts.html#the-nonlocal-statement)
- [Python built-in functions: `locals()`](https://docs.python.org/3.14/library/functions.html#locals)
- [Python `symtable`: access to compiler symbol tables](https://docs.python.org/3.14/library/symtable.html)
- [Python programming FAQ: `UnboundLocalError`](https://docs.python.org/3.14/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value)
