Variable Scope and Namespace
The same spelling can appear in the outermost namespace and inside a function, but assignment inside the function is local by default.
Try it: Scope: Local, Enclosing, Global, Built-in
How Python looks up a name Local → Enclosing → Global → Built-in, why assigning to a name inside a function makes it local (and raises UnboundLocalError if it is read first), and what global, nonlocal and closures change.
How it works
- Before a function runs, Python marks every name assigned inside it as local to that function, unless it is declared global or nonlocal.
- Reading a name searches the local namespace, then enclosing functions, then the module's globals, then the built-ins; the first hit wins.
- A local name read before it has a value raises UnboundLocalError (Python does not fall back to the global).
- global x makes assignments go to the module namespace; nonlocal x makes them go to the nearest enclosing function.
- A function's local frame disappears when it returns, unless an inner function (a closure) still uses its variables.
Default run (10 steps): Ready: Local shadows global. Every name is looked up Local, then Enclosing, then Global, then Built-in. … Finished. Printed: inside: 99 / outside: 10.
Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. Built-ins shows only the names the script used.
Loading the simulation…
A Name Can Belong to Different Scopes
A variable name does not automatically refer to one universally shared place. Python organizes names through namespaces, and a function has its own local namespace while the program also has an outermost namespace. The important rule for this lesson is that Python localizes a variable by default when a function assigns to that name. This is why a function cannot modify a top-level variable through assignment unless it explicitly uses the global statement.
The same spelling can appear in the outermost namespace and inside a function, but assignment inside the function is local by default.
Trace an Assignment Without global
What do you think happens?
Suppose the outermost namespace contains score with the value 10. A function assigns score = 20 without a global statement. Which binding does that assignment change?
Reveal answer
A local score binding inside the function
Python localizes names by default when a function assigns to them. The assignment does not modify the outermost variable.
score = 10
def set_score():
score = 20
set_score()- 1Before the call, the outermost score binding has the value 10.
- 2During the call, the assignment creates or updates a local score binding with the value 20.
- 3After the call, the local binding is no longer the binding being used, and the outermost score remains 10.
- The outermost namespace binds score to 10.
- The function assigns to a name called score.
- Because there is no global statement, this assignment is local to the function.
- The function call completes without changing the outermost score.
Reading Versus Writing
Reading a global variable from inside a function works without global. Writing to that global variable requires the global statement. This distinction matters when a function both reads and writes a name: without global, Python treats the assigned name as local, so the function cannot use that same name as an outer-scope value in the intended way. The source identifies this mixed read-and-write situation as causing UnboundLocalError.
The function can read the outermost limit without declaring global.The global statement makes the function's count name refer to the outermost count binding, so the assignment can modify it.Declare the Shared Binding
The global statement creates an explicit binding between a local name and a global name. Place the statement inside the function before the assignment that must change the outermost variable. After that declaration, the function has both read and write access to the outer-scope variable.
visits = 0
def record_visit():
global visits
visits = visits + 1
record_visit()
record_visit()- 1Initially, the outermost visits binding has the value 0.
- 2The first call reads 0 and assigns 1 to the global binding.
- 3The second call reads 1 and assigns 2 to the same global binding.
- 4The function can perform these writes because it uses global visits.
- visits is defined in the program's outermost namespace.
record_visitdeclares that its visits name is bound to the global name.- The assignment reads the current global value and writes back an increased value.
- Each call changes the shared outermost value.
Use global only when a function genuinely needs to modify a variable defined in the program's outermost namespace. Keeping the declaration close to the function's assignment makes the shared state visible to readers of the code.
Observe the State Change
status = "waiting"
def start_task():
global status
status = "running"Before the function call, the outermost status value is waiting.
status = "waiting"
def start_task():
global status
status = "running"
start_task()After the call, the outermost status value is running.
This example shows the defining effect of global: the function is not merely creating a temporary local name. It is changing shared state in the outermost namespace, and later code that uses that outermost name can observe the new value.
Clarity and Safer Data Flow
A global variable can be convenient, but global state reduces code clarity and testability. A function that changes shared state has an effect that is not visible from its parameters or return value. This makes it harder to see where data comes from and where it changes. The recommended default is to pass data through parameters and return values, using global only when necessary.
| Approach | Data movement | Clarity concern |
|---|---|---|
| Read a global value | The function obtains a value from outer-scope state | The dependency is outside the parameter list |
| Write a global value | The function changes shared outer-scope state | The side effect may be harder to track and test |
| Use parameters and return values | The caller supplies data and receives a result | The data flow is more explicit |
Global state can be used, but explicit inputs and outputs generally make a function's data flow clearer.
The first version changes shared state through global. The second version makes the input and result explicit with a parameter and a return value. When the second design fits the task, it is usually easier to understand and test because the function's data flow is visible at the call site.
Mistakes with Global Names
Common mistake Expecting assignment inside a function to update the outermost variable automatically.
What it looks likescore = 10 def change_score(): score = 20Why it failsPython localizes variables by default on assignment, so the assignment creates or updates a local binding.
Do this insteadUse global score before the assignment if modifying the outermost score is genuinely required.
Common mistake Reading and writing a global name without declaring it global.
What it looks likecount = 0 def increase(): count = count + 1Why it failsThe source identifies mixing reads and writes without global as causing
UnboundLocalError.Do this insteadDeclare global count before the assignment, or redesign the function to receive count as a parameter and return the new value.
Common mistake Using global for every value shared with a function.
What it looks likeA function changes shared state even though the caller could have passed the value as an argument.
Why it failsGlobal state reduces code clarity and testability.
Do this insteadPrefer passing data through parameters and return values when that communicates the data flow clearly.
Practice the Binding Decision
For each situation, decide whether the function needs global. Then explain why.
- A function only reads an outermost variable called limit.
- A function assigns a new value to an outermost variable called mode.
- A function receives a value as a parameter and returns an updated value without changing any outermost variable.
- A function both reads and assigns count using the same name, but has no global statement.
Hints
- Reading a global variable does not require global.
- Writing to a global variable requires global.
- Parameters and return values make data flow explicit.
- The mixed read-and-write case without global causes
UnboundLocalError.
Choosing a binding strategy
A program has an outermost variable called attempts. A function should increase it each time it is called. What must the function do, and what alternative design could make the data flow clearer?
- 1Identify the operation
The function must write a new value to a variable defined in the outermost namespace.
- 2Apply the binding rule
Assignment inside a function is local by default, so the function needs global attempts before assigning to the outermost attempts.
- 3Consider the design
Because global state can reduce clarity and testability, another design is to pass attempts as a parameter and return the increased value.
Use global attempts when modifying the shared outermost variable is necessary; otherwise prefer an explicit parameter-and-return-value design.
Key Takeaways
- 1Python localizes a name by default when a function assigns to it.
- 2Reading a global variable works without global, but writing to it requires global.
- 3The global statement explicitly binds the function's name to the outermost name, allowing reads and writes.
- 4Mixing reads and writes without global causes
UnboundLocalError. - 5Global state can reduce clarity and testability, so prefer parameters and return values when they make data flow explicit.
At a glance
- Assignment inside a function is local by default.
- Use global before assigning to a variable defined in the program's outermost namespace.
- Reading a global does not require global, but mixing a read and write without it causes
UnboundLocalError. - Use global sparingly because shared global state can reduce clarity and testability.
- Prefer parameters and return values when they provide a clearer data flow.