Python with AI Tutorial · Chapter 22 of 48
Python scope is the set of rules that decides where a variable can be seen and changed. A variable created inside a function has local scope and disappears when the function ends; a variable created at the top of a file has global scope. The global and nonlocal keywords let a function deliberately modify variables that live outside it.
Local scope
Any variable assigned inside a function belongs to that function. It is created when the function runs and destroyed when it returns. Code outside the function cannot see it.
def calculate_bonus(salary):
bonus = salary * 0.1 # local variable
return bonus
print(calculate_bonus(50000))
print(bonus)
Output: 5000.0 Traceback (most recent call last): File "<stdin>", line 6, in <module> NameError: name 'bonus' is not defined
The NameError on the last line is Python protecting you. Each call to calculate_bonus gets its own fresh bonus, so two calls can never interfere with each other, and no other part of the program can accidentally read a half-finished value.
Global scope
Variables defined at the top level of a file, outside every function, are global. Functions can read them without any special syntax, which is convenient for settings and constants.
TAX_RATE = 0.18
COMPANY = "NeoTech Supplies"
def invoice_line(amount):
return f"{COMPANY}: {amount} + tax = {amount * (1 + TAX_RATE):.2f}"
print(invoice_line(1000))
print(invoice_line(250))
Output: NeoTech Supplies: 1000 + tax = 1180.00 NeoTech Supplies: 250 + tax = 295.00
By convention, global values that should never change are written in capitals. Python does not enforce this, but it signals to readers that TAX_RATE is a constant rather than a variable that the program updates.
The LEGB rule
When Python meets a name it searches four layers in order and stops at the first match. The order is remembered as LEGB.
| Layer | Stands for | Where the names live |
|---|---|---|
| L | Local | Inside the current function |
| E | Enclosing | In any outer function that wraps the current one |
| G | Global | At the top level of the module (file) |
| B | Built-in | Names Python provides, such as print, len, sum |
Because the local layer is searched first, a local variable with the same name as a global one hides it inside that function. This is called shadowing, and it is the source of many confusing bugs.
Shadowing a global with a local
Assigning to a name inside a function creates a new local variable, even if a global with the same name exists. The global is left untouched.
region = "Global HQ"
def set_region():
region = "APAC" # new local variable, not the global
print("Inside:", region)
set_region()
print("Outside:", region)
Output: Inside: APAC Outside: Global HQ
total = total + 1, raises UnboundLocalError. The assignment makes total local for the whole function, so the read on the right side finds a local that does not exist yet. Either pass the value in and return it, or declare global total.The global keyword
global tells Python that a name inside the function refers to the module-level variable, so assignments update it rather than creating a local. Use it sparingly; functions that quietly change shared state are hard to test.
orders_processed = 0
def process_order(order_id):
global orders_processed
orders_processed += 1
print(f"Order {order_id} done")
process_order(5001)
process_order(5002)
print("Total processed:", orders_processed)
Output: Order 5001 done Order 5002 done Total processed: 2
A cleaner alternative is to have the function return the new count, or to wrap the counter in a class. Both make the data flow visible at the call site instead of hiding it inside the function.
Paste a function that fails with UnboundLocalError and ask the assistant to explain the cause and show a version without the global keyword.
This Python code raises UnboundLocalError: count = 0; def add_sale(): count += 1. Explain step by step why Python treats count as local, then show two fixes: one using the global keyword and one that avoids global by returning the new value. Which one would you recommend for a sales-tracking script and why?
Enclosing scope and nested functions
A function defined inside another function can read the outer function’s variables. This enclosing scope is the E in LEGB. The inner function is called a closure when it remembers those variables after the outer function has finished.
def make_discounter(percent):
def apply(price):
return round(price * (1 - percent / 100), 2)
return apply
staff_discount = make_discounter(20)
partner_discount = make_discounter(35)
print(staff_discount(1200))
print(partner_discount(1200))
Output: 960.0 780.0
Each call to make_discounter creates a separate percent, and each returned apply keeps its own. This is the same mechanism that made the lambda factory work in the previous chapter.
The nonlocal keyword
Reading an enclosing variable is automatic, but assigning to it needs nonlocal. Without it, the assignment would create a new local inside the inner function, just as it does with globals.
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
tickets = make_counter()
print(tickets(), tickets(), tickets())
refunds = make_counter()
print(refunds())
Output: 1 2 3 1
tickets and refunds each hold their own private count. This is a lightweight way to keep state without a global variable and without writing a class.
Built-in scope and shadowing built-ins
The outermost layer holds Python’s built-in names. Nothing stops you from reusing them, but doing so hides the original for the rest of the file.
sum = 0 # shadows the built-in sum()
for amount in [120, 80]:
sum += amount
print(sum)
del sum # restore the built-in
print(sum([120, 80]))
Output: 200 200
Names such as list, str, max, id and type are tempting variable names. Prefer total, items or employee_id so the built-ins stay available.
Ask the assistant to build a closure-based counter with nonlocal and then to explain how it differs from a global counter and from a class.
Write a Python function make_invoice_numberer(prefix) that returns an inner function; each call to the inner function should return the next invoice number like "INV-2026-001", "INV-2026-002". Use nonlocal for the counter. Then explain in plain English why nonlocal is needed, and compare this closure approach with a global counter and with a small class.
Common mistakes
- Expecting a variable created inside a function to exist after the function returns.
- Assigning to a global inside a function without
global, which silently creates a local copy. - Reading then assigning the same name in one function, producing
UnboundLocalError. - Using
globalwherenonlocalis needed inside a nested function. - Naming variables
list,sumormaxand losing access to the built-in.
Exercise
Write a function make_budget(limit) that returns an inner function spend(amount). Each call to spend should subtract the amount from the remaining budget using nonlocal and return the remaining balance. Create a marketing budget of 5000, spend 1200 and then 800, and print the balance after each call.
Show answer
def make_budget(limit):
remaining = limit
def spend(amount):
nonlocal remaining
remaining -= amount
return remaining
return spend
marketing = make_budget(5000)
print(marketing(1200))
print(marketing(800))
Output: 3800 3000
Related chapters
FAQ
What is the difference between local and global scope in Python?
A local variable is created inside a function and exists only while that function runs. A global variable is defined at the top level of the file and can be read from any function in that file.
When should I use the global keyword in Python?
Only when a function must assign to a module-level variable, and even then consider returning the value instead. Reading a global never needs the keyword.
What does nonlocal do in Python?
nonlocal lets a nested function assign to a variable that belongs to its enclosing function, rather than creating a new local. It is used to keep state inside closures.
Working with spreadsheets too? Ready-made Excel, Google Sheets and Power BI templates are at NextGenTemplates.com.
Chapter 22 of 48 · Python with AI: all 48 chapters



