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)The last line fails with a NameError: message only existed while make_greeting() was running. Each call gets a fresh set of local variables, and they disappear when the function returns. That's a good thing: two functions can both use a variable called total without interfering with each other.
Parameters are local variables too:
def double(n):
n = n * 2
return n
number = 5
print(double(number))
# unchanged: only the local n changed
print(number)When you call double(number), the parameter n starts out referring to the same value as number. n = n * 2 then points n at a new value, 10. That reassignment only affects the local name n; number still refers to 5.
Lists behave differently in one way. If a function changes a list it was given (with append, for example) rather than assigning a new value to the parameter, the caller sees the change, because both names refer to the same list:
def add_bonus(scores):
scores.append(100)
my_scores = [80, 90]
add_bonus(my_scores)
print(my_scores)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))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():
# a NEW local variable
count = 0
print("inside:", count)
reset()
# the global is untouched
print("outside:", count)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()The error is an UnboundLocalError: "cannot access local variable 'count'". Python decides which names are local before the function runs, by looking for assignments. It sees count = ..., so count is local everywhere in increment. When it then evaluates the right side, count + 1, the local count has no value yet. It never falls back to the global one.
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)global count tells Python "count in this function means the global variable", so count += 1 changes the variable outside. Without that line, you'd get the UnboundLocalError from above.
You only need global to assign a new value to a global name. Changing a global list in place, with append for example, works without it, because you're not assigning to the name:
log = []
def record(event):
log.append(event)
record("start")
record("stop")
print(log)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)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()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())make_counter() runs once: it creates count = 0, defines increment and returns the function itself (no parentheses, so it isn't called). counter now refers to that inner function. Each counter() call adds 1 to the same count, giving 1, 2, 3.
The inner function "remembers" count even after make_counter has finished. This is called a closure, and you'll see it again with decorators. nonlocal works like global, but for the enclosing function's variable instead of a global one.
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()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] # hides the built-in sum() sum = 0 print(sum(numbers))
The error is a TypeError: "'int' object is not callable". After sum = 0, the name sum finds your 0 first (global comes before built-in), and you can't call a number.
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)Assigning to x inside the function creates a new local variable, separate from the global x.
The global x is never changed, so think about what the last line prints.
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)Because score is assigned inside the function, Python treats it as local, so reading it first fails.
Add global score as the first line inside the function.
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)Give the function a score parameter and return the new score.
Call it as score = add_points(score, 5) so the result is stored.
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))total belongs to make_accumulator, so the inner function must say nonlocal total before changing it.
Then add n to total and return total.
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 namelets a function change a global variable. Prefer passing values in and returning results.nonlocal namelets 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.
Know something worth teaching? Write your own tutorial with runnable examples and share it.