Learn Python / Scope

Scope

Where a variable is created decides where it can be used. That's called its scope. Understanding scope explains some confusing errors, and it helps you write functions that don't accidentally interfere with each other.

Local variables

A variable created inside a function is local to that function. It exists only while the function runs, and code outside can't see it:

def make_greeting():
    message = "Hello from inside!"
    print(message)

make_greeting()
print(message)
Output

Each call gets a fresh set of local variables, and they disappear when the function returns.

Parameters are local variables too:

def double(n):
    n = n * 2
    return n

number = 5
print(double(number))
print(number)   # unchanged: the function changed its own local n
Output

Global variables

A variable created at the top level of your program, outside any function, is global. Functions can read global variables:

tax_rate = 0.2

def price_with_tax(price):
    return price * (1 + tax_rate)

print(price_with_tax(100))
Output

Same name, different variables

If a function assigns to a name, Python treats that name as local throughout the function, even if a global variable has the same name. The two are completely separate:

count = 10

def reset():
    count = 0          # a NEW local variable
    print("inside:", count)

reset()
print("outside:", count)   # the global is untouched
Output

This leads to a classic error. Because count is assigned inside the function, Python treats it as local everywhere in that function. So reading it before the assignment fails:

count = 10

def increment():
    count = count + 1
    return count

increment()
Output

The global keyword

To change a global variable from inside a function, declare it with global:

count = 0

def increment():
    global count
    count += 1

increment()
increment()
print(count)
Output

Use global sparingly. Functions that quietly change global variables are hard to follow and hard to test. It's usually cleaner to pass values in and return the result:

def increment(count):
    return count + 1

count = 0
count = increment(count)
count = increment(count)
print(count)
Output

Nested functions and nonlocal

Functions can be defined inside other functions. The inner function can read the outer function's variables:

def outer():
    message = "from outer"

    def inner():
        print("inner sees:", message)

    inner()

outer()
Output

To change a variable of the enclosing function, use nonlocal:

def make_counter():
    count = 0

    def increment():
        nonlocal count
        count += 1
        return count

    return increment

counter = make_counter()
print(counter())
print(counter())
print(counter())
Output

The inner function "remembers" count even after make_counter has finished. This is called a closure, and you'll see it again with decorators.

The LEGB rule

When Python looks up a name, it searches four scopes in order and uses the first match:

Order Scope What it is
1 Local Inside the current function
2 Enclosing Inside any outer function
3 Global The top level of the program
4 Built-in Python's own names, such as print and len
x = "global"

def outer():
    x = "enclosing"

    def inner():
        x = "local"
        print(x)

    inner()

outer()
Output

Delete the x = "local" line and run again, then delete x = "enclosing" too. Python finds the next x along the chain each time.

Don't shadow built-ins

Because built-ins come last in the search, a variable named list, sum or max hides the built-in function:

numbers = [3, 1, 2]
sum = 0              # hides the built-in sum()
print(sum(numbers))
Output

Pick names like total or values instead.

Exercises

Exercise 1: Predict the output

Predict what each print shows, then run it to check.

x = 5

def change():
    x = 99
    print("inside:", x)

change()
print("outside:", x)
Output

Exercise 2: Fix the UnboundLocalError

This raises an error. Fix it with the global keyword so the program prints Score: 15.

score = 10

def add_points(points):
    score = score + points

add_points(5)
print("Score:", score)
Output

Exercise 3: Without global

Rewrite the previous program without global: make add_points take the score as a parameter and return the new score.

score = 10

def add_points(points):
    score = score + points

add_points(5)
print("Score:", score)
Output

Exercise 4: Running total

Complete make_accumulator() so it returns a function that adds each number it receives to a running total and returns the total. The calls should print 10, 15 and 35.

def make_accumulator():
    total = 0

    def add(n):
        # your code here
        pass

    return add

acc = make_accumulator()
print(acc(10))
print(acc(5))
print(acc(20))
Output

Summary

  • Variables created inside a function are local: they exist only during that call.
  • Top-level variables are global. Functions can read them freely.
  • Assigning to a name inside a function makes it local, which can cause an UnboundLocalError.
  • global name lets a function change a global variable. Prefer passing values in and returning results.
  • nonlocal name lets an inner function change a variable of its enclosing function.
  • Python looks names up in the order Local, Enclosing, Global, Built-in (LEGB). Avoid naming variables after built-ins.