Python
variable scope
nonlocal
nested functions
programming concepts

Is it possible to modify a variable in python that is in an outer enclosing, but not global, scope?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Yes. In Python 3, the direct way to rebind a variable from an enclosing, non-global scope is the nonlocal keyword. Without nonlocal, assignment inside the inner function creates a new local variable instead of changing the binding from the outer function.

The Scope Rule Behind The Problem

Python resolves names using the LEGB rule:

  • local
  • enclosing
  • global
  • built-in

Reading an enclosing variable is easy. Rebinding it is the part that surprises people.

Consider this example:

python
1def outer():
2    count = 0
3
4    def inner():
5        count += 1
6        return count
7
8    return inner()
9
10print(outer())

This raises UnboundLocalError. The reason is that count += 1 is an assignment. Once Python sees an assignment to count inside inner, it treats count as a local variable in that function. Then the right-hand side tries to read the local variable before it has a value.

Use nonlocal For Rebinding

The fix is to tell Python that the name lives in the enclosing function scope.

python
1def outer():
2    count = 0
3
4    def inner():
5        nonlocal count
6        count += 1
7        return count
8
9    print(inner())
10    print(inner())
11    print(inner())
12
13outer()

This prints:

text
1
2
3

nonlocal count means assignments to count inside inner should update the variable from the nearest enclosing function scope that already defines count.

nonlocal Is Not global

nonlocal only targets enclosing function scopes. It does not reach module-level names. global is the keyword for rebinding a module-level variable.

python
1value = 10
2
3def outer():
4    value = 20
5
6    def inner():
7        nonlocal value
8        value += 5
9        return value
10
11    return inner()
12
13print(outer())
14print(value)

This prints 25 and then 10. The inner function changed the variable in outer, not the module-level variable.

That distinction matters because global is often overused when nonlocal is the correct, narrower tool.

The Enclosing Variable Must Already Exist

nonlocal cannot create a brand-new name in the outer scope. The name must already be bound in an enclosing function.

This is invalid:

python
1def outer():
2    def inner():
3        nonlocal count
4        count = 1

Python rejects it because there is no enclosing count to rebind. You must define the name first in the outer function.

python
1def outer():
2    count = 0
3
4    def inner():
5        nonlocal count
6        count = 1
7        return count
8
9    return inner()

Mutable Objects Are A Different Case

If you are not rebinding the outer name, you may not need nonlocal at all. Mutating a mutable object works because the name itself is not reassigned.

python
1def outer():
2    items = []
3
4    def inner():
5        items.append("x")
6        return items
7
8    print(inner())
9    print(inner())
10
11outer()

This works without nonlocal because items.append(...) mutates the list object. The variable items still points to the same list.

That difference is important:

  • rebinding a name usually needs nonlocal
  • mutating the object behind the name usually does not

A Common Closure Pattern

One clean use of nonlocal is building stateful functions.

python
1def make_counter(start=0):
2    value = start
3
4    def next_value():
5        nonlocal value
6        value += 1
7        return value
8
9    return next_value
10
11counter = make_counter(5)
12print(counter())
13print(counter())
14print(counter())

This is a genuine closure. The returned function keeps access to value even after make_counter has finished executing.

For simple state, this is fine. If the behavior grows more complex, a class may become easier to read and test.

Older Python And Workarounds

In Python 2, nonlocal does not exist. Older code sometimes worked around that with a mutable container.

python
1def outer():
2    count = [0]
3
4    def inner():
5        count[0] += 1
6        return count[0]
7
8    return inner()

This works because the list is mutated, not rebound. In modern Python 3 code, nonlocal is usually clearer and should be preferred when the intent is rebinding an enclosing variable.

Common Pitfalls

  • Forgetting that assignment inside the inner function creates a local binding by default.
  • Using global when the real target is the enclosing function scope.
  • Declaring nonlocal for a name that does not exist in any enclosing function.
  • Assuming mutation and rebinding are the same thing.
  • Using closure state when a small class would make the design easier to understand.

Summary

  • Yes, Python can modify a variable from an enclosing non-global scope.
  • In Python 3, use nonlocal for rebinding that variable.
  • 'nonlocal targets enclosing function scope, while global targets module scope.'
  • Mutating a mutable outer object often works without nonlocal.
  • If the shared state becomes complex, consider a class instead of nested functions.

Course illustration
Course illustration

All Rights Reserved.