Python Scope: Local, Global and nonlocal Variables - Python with AI tutorial chapter 22
Python

Python Scope: Local, Global and nonlocal Variables

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
Common mistake: reading a global and then assigning to it in the same function, for example 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.

Try it with AI

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.

Try it with AI

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 global where nonlocal is needed inside a nested function.
  • Naming variables list, sum or max and 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

PK
Meet PK, the founder of NeotechNavigators.com! With over 15 years of experience in Data Visualization, Excel Automation, and dashboard creation. PK is a Microsoft Certified Professional who has a passion for all things in Excel. PK loves to explore new and innovative ways to use Excel and is always eager to share his knowledge with others. With an eye for detail and a commitment to excellence, PK has become a go-to expert in the world of Excel. Whether you're looking to create stunning visualizations or streamline your workflow with automation, PK has the skills and expertise to help you succeed. Join the many satisfied clients who have benefited from PK's services and see how he can take your data analysis skills to the next level!
https://neotechnavigators.com