Embercode
✕
Functions and ScopeLesson 4 of 17

46 min read

Variable scope and lifetime in Python

Why it matters

Many beginner errors say NameError: name 'x' is not defined. The name exists in the file, but the code that uses it is in a place where Python cannot find it. Scope tells you where a name is visible, and why one part of a program cannot see a name from another part. Knowing scope lets you read code quickly and predict errors before you run it. It also explains why a function can reuse a name such as i or count without changing the rest of the program.

Python's scope rules are different from Java's in one main way. Java ends a variable's scope at the closing brace of any block. Python ends a local name at the end of the function. A for loop or an if block does not hide the names it creates. This lesson shows what that means in practice.

The idea: where a name is known

A name in Python is a label that points to a value. The scope of a name is the part of the program where Python can find that label. The lifetime of a value is the time it stays in memory. The two are related, but they are not the same.

Think of a function call as a desk. A sticky note written on the desk is visible only while the desk is in use. When the call ends, the desk is cleared, and the notes are gone. A name made inside a function is a sticky note on that desk. A name made at the top of the file is written on a board in the hallway, which everyone can read for the whole run of the program.

Names inside a function

A name assigned inside a function is a local name. It belongs to that call only. No other function can see it, even another function in the same file.

Python · editable

Output:

10
99

Both functions assign a name x. The two names do not interact, because each one lives in its own call. If second tried to read the x from first, Python would fail:

def second():
    print(x)
 
def first():
    x = 10
 
first()
second()

Output:

Traceback (most recent call last):
  File "/path/to/main.py", line 8, in <module>
    second()
    ~~~~~~^^
  File "/path/to/main.py", line 2, in second
    print(x)
          ^
NameError: name 'x' is not defined

The first part of each File line shows the folder on your machine, and the line numbers follow your file. The last line is the message to recognise. Python could not find a name called x from inside second, so it stopped. The x that first created was never visible to second.

Reusing a name in different functions is safe and common. Two functions can each have a counter called i, a total called total, or a parameter called n. Each call works with its own copy, and the names never clash.

Parameters are local names too. A parameter exists from the start of the call until the call ends. The body of the function can use it, but code outside the function cannot. A helper function cannot read a variable from its caller unless the caller passes it in as an argument.

Java note: Java has no separate parameter rule. A parameter there is a local variable, and the same is true in Python.

Blocks do not create a scope

In Java, a variable declared inside a loop body or an if block is gone when the block ends. In Python, for, while, if, elif, else, try and with do not create a new scope. A name created inside one of these blocks stays visible after the block ends.

Python · editable

Output:

14
3
9

The loop runs three times. In each pass, i takes the values 1, 2 and 3, and square is recalculated. After the loop, i still exists, and it holds the last value the loop gave it, which is 3. square also still exists, with its last value, 9. A Python programmer usually does not read these names after the loop, because the values can be confusing, but Python does not stop you.

The same rule applies to if blocks. A name made inside a branch exists after the branch ends, but only if the branch ran. This is the case that catches beginners:

score = 30
if score >= 50:
    result = "pass"
print(result)

Output:

Traceback (most recent call last):
  File "/path/to/main.py", line 4, in <module>
    print(result)
          ^^^^^^
NameError: name 'result' is not defined

The branch score >= 50 was not taken, so result was never assigned. The name does not exist at all. Python does not reserve it. The same error happens with a loop that runs zero times:

for i in range(0):
    pass
print(i)

Output:

Traceback (most recent call last):
  File "/path/to/main.py", line 3, in <module>
    print(i)
          ^
NameError: name 'i' is not defined. Did you mean: 'id'?

The loop body never ran, so i was never assigned. The suggestion Did you mean: 'id' is Python guessing at a typo. It is not a real match for your code.

The safe habit is to set a starting value before a loop or an if, when you need the name afterwards:

Python · editable

Output:

pass

The name is set before the if, so it always exists. The branch only changes its value.

Looking up a name: the LEGB order

When Python sees a name, it searches four places in order. The order is Local, Enclosing, Global and Built-in, which is called the LEGB rule. Python stops at the first place where it finds the name.

  • Local: names assigned inside the current function.
  • Enclosing: names in any function that contains the current function.
  • Global: names assigned at the top level of the file, outside every function.
  • Built-in: names that Python provides, such as print, len and range.
Python · editable

Output:

enclosing
global

inner has no local name, so Python looks in outer, which is the enclosing function, and finds enclosing. The top-level print(name) looks only at the global level, so it prints global.

The built-in names are the last place Python looks. A common mistake is to create a global name with the same name as a built-in, which hides the built-in for the rest of the file:

list = [1, 2, 3]
print(list)
print(list("abc"))

Output:

[1, 2, 3]
Traceback (most recent call last):
  File "/path/to/main.py", line 3, in <module>
    print(list("abc"))
          ~~~~^^^^^^^
TypeError: 'list' object is not callable

After list = [1, 2, 3], the name list points to a list, not to the built-in type that turns text into a list. Calling it fails. Avoid naming variables list, dict, str, sum or max. Pick a name that describes the data, such as items or scores.

To see which local names a function has, call locals() inside it. It returns a dictionary, which is a table of name and value pairs:

Python · editable

Output:

{'a': 2, 'b': 3, 'total': 5}

The parameters a and b are local names, and so is total. The result shows the names and their values at the moment of the call.

Global names

A name assigned at the top level of a file is a global name. Every function in the file can read it, without any special word.

Reading a global name is allowed. Assigning to a name inside a function creates a local name, even when a global with the same name exists. This is the rule that causes the most confusion. The next program makes a local count that hides the global one:

Python · editable

Output:

5
100

Inside show, the local count hides the global count, so show prints 5. After the call, print(count) at the top level finds the global, which still holds 100. Python does not warn you that the name is hidden. This is called shadowing. Shadowing is legal, and it causes bugs when a reader expects the global.

If a function needs to change a global, it must declare that with global. Without it, += fails:

counter = 0
 
def tick():
    counter += 1
 
tick()
print(counter)

Output:

Traceback (most recent call last):
  File "/path/to/main.py", line 6, in <module>
    tick()
    ~~~~^^
  File "/path/to/main.py", line 4, in tick
    counter += 1
    ^^^^^^^
UnboundLocalError: cannot access local variable 'counter' where it is not associated with a value

counter += 1 is the same as counter = counter + 1. Because the function assigns counter, Python treats counter as local to tick. The local name has no value yet when the right side is read, so the error says the variable is not associated with a value. The fix is to declare the name as global:

Python · editable

Output:

3

The global calls line tells Python that calls inside tick means the top-level name. Each call adds 1 to the same value, so the total is 3. Use global only when a function really must change shared state. Most functions should take their inputs as parameters and return their results. The same caution applies to every shared value: any function can change it, so changes can be hard to trace.

Java note: Python has no static keyword. A module-level variable plays the role of a Java static field. Class attributes, which play a similar role, come in the object-oriented modules.

Enclosing names and nonlocal

A function can be defined inside another function. The inner function can read the names of the outer one. If it needs to change one of those names, it uses the word nonlocal. This is the E step of the LEGB order, and it lets you keep state inside a function without using a global.

Python · editable

Output:

1
2
3
1

make_counter creates a variable count and an inner function step. It returns step. After make_counter finishes, step still remembers count, because step refers to it. A function that remembers names from its enclosing function is called a closure.

nonlocal count says that count += 1 changes the count of make_counter, not a new local. Each call to make_counter makes a new count. That is why other starts again at 1, while next_id keeps counting from 3.

nonlocal only works inside a function. Written at the top level of a file, it is an error:

nonlocal total
total = 1

Output:

  File "/path/to/main.py", line 1
    nonlocal total
    ^^^^^^^^^^^^^^
SyntaxError: nonlocal declaration not allowed at module level

A SyntaxError means Python cannot read the file at all, so none of it runs. Use global at the top level, or move the code into a function.

Lifetime: how long a value lives

A local name lasts for one call. When the call ends, its names are removed, and the values they pointed to can be freed if nothing else refers to them. Every call starts with a fresh set of local names. A local never remembers its value from the last call. Look at this function:

def total_items():
    count = 0
    count = count + 1
    return count

Calling it three times returns 1 each time. Each call starts count at 0 again. A name that must survive between calls needs one of two things: a global, or a closure that holds it, as make_counter did.

A global name lasts for the whole run of the program. It exists from the line where it is assigned until the program ends, or until the name is removed with del:

Python · editable

Output:

NameError: name 'name' is not defined

del name removes the name name from the program. After that, looking up name fails the same way it does for a name that was never made. You rarely need del in ordinary code, but it shows that a name and the value it points to are separate things.

A value can also outlive the function that made it, when the function returns it. The name inside the function ends, but the caller keeps the value:

def make_list():
    items = [1, 2, 3]
    return items
 
saved = make_list()
print(saved)

Output:

[1, 2, 3]

The name items ended with the call. The list lives on, because saved refers to it.

Comprehensions have their own scope

A list comprehension is a short way to build a list from a loop. It has its own scope, so the loop variable does not leak out of it:

squares = [i * i for i in range(4)]
print(squares)
print(i)

Output:

[0, 1, 4, 9]
Traceback (most recent call last):
  File "/path/to/main.py", line 3, in <module>
    print(i)
          ^
NameError: name 'i' is not defined. Did you mean: 'id'?

The list is built correctly, but i exists only inside the comprehension. A plain for loop does not behave this way, which is one of the places where Python's rules are easy to mix up.

Common mistakes

Reading a name that was set only inside an if branch that did not run. Set a starting value before the if. The error is NameError: name ... is not defined.

Reading a name after a loop that ran zero times. Set the name before the loop.

Assigning to a global name inside a function without global. The error is UnboundLocalError: cannot access local variable ... where it is not associated with a value. Add global name to the function, or pass the value in and return the new one.

Expecting a local to remember its value from the last call. Each call starts fresh. Use a global or a closure only when the value must persist.

Naming a variable list, dict, str or sum, which hides the built-in. The error is often TypeError: 'list' object is not callable.

Using a name that a function reads before the function assigns it. Python decides which names are local by reading the whole function before any line runs. In count = count + 1, the name count is local, so the right side reads a local that has no value yet.

Variations and edge cases

Python does not have block scope, so a name made inside a loop can be reused by the next loop without a clash. Two loops that both use i do not interfere when they run one after the other. The second loop simply reassigns i.

A function that both reads a name and assigns it is one where the local-or-global question matters most. If you need the global's value and also want a new local with the same name, pick a different name for the local. Readers should not have to think about which one they are looking at.

Python allows you to redefine a name in the same function without any error. This is a difference from Java, where the same declaration twice in one method is a compile error. Python will accept it, so a second assignment silently replaces the first.

Interview-style questions

An interviewer might ask, "What is the scope of a variable made inside a for loop in Python?" The answer is that the loop does not create a scope, so the name lives in the enclosing function and stays after the loop. Give the example of i after for i in range(3).

They might ask, "What does LEGB mean?" Name the four places in order: local, enclosing, global and built-in.

A third question is, "Why does counter += 1 fail inside a function?" The answer is that the assignment makes counter local to the function. The right side reads a local that has no value yet. The fix is global counter.

Finally, expect a question about closures: "How can a function keep a count between calls without a global?" Describe the inner function with nonlocal, and say the outer function's variable lives on because the inner function refers to it.

Real-world uses

Scope keeps large programs manageable. When a team writes a payment system, each function can use its own names without worrying about clashes in other parts of the code. A variable named total in the tax function does not change the total in the invoice function.

Scope also decides how safe a change is. A local name used only inside one function can be changed without checking the rest of the file. A global name can be changed by any function, so changing it can affect code far away. Professional programmers keep scope as small as possible for this reason.

Closures are used in many small helpers, such as a function that returns a function with a fixed setting already filled in. The inner function remembers the setting, and the caller does not have to pass it again.

Practice

  1. Write a loop that sums the numbers from 1 to 10. Make the sum available after the loop, and print it. Predict the answer before you run it.

Show the answer

total = 0
for i in range(1, 11):
    total += i
print(total)

The output is 55. The name total is set before the loop. The loop variable i also still exists after the loop, and it holds 10.

  1. Predict the output of a program where a global x is 3, and a function helper assigns its own x to 8 and prints it. After helper returns, the top level prints x. Write both lines.

Show the answer

8
3

The assignment inside helper makes a new local x. The top-level x is not changed, so it still holds 3.

  1. Write a function count_calls that adds 1 to a global calls each time it runs. Call it five times, and print calls at the end.

Show the answer

calls = 0
 
def count_calls():
    global calls
    calls += 1
 
for _ in range(5):
    count_calls()
print(calls)

The output is 5. The global line lets the function change the top-level name.

  1. A function report sets total = 5, prints it, and returns nothing. Call it twice. Predict both outputs.

Show the answer

5
5

The local total starts fresh on every call. The second call does not remember the first.

  1. Explain why print(i) after for i in range(0): fails with NameError, even though i is a name in the loop header. Give the message.

Show the answer

The loop body never runs when the range is empty, so Python never assigns i. The message is NameError: name 'i' is not defined. Set i = 0 before the loop if you need a value after it.

  1. Predict the output: a global value is 50. A function sets its own value = 2 and prints value * 3. Then the top level prints value. Write both lines.

Show the answer

6
50

The local value hides the global inside the function, so value * 3 is 6. At the top level, the global still holds 50.

  1. Write a function label_for(number) that returns "positive", "negative" or "zero". Use a variable declared before the if chain, and test it with -2.

Show the answer

Python · editable

The output is negative. The name label has a starting value, so the function returns a value on every path.

  1. A global total is 10. A function increase() with no parameters runs total = total + 5 and returns total. Predict the error, and write a version that works without global.

Show the answer

The error is UnboundLocalError: cannot access local variable 'total' where it is not associated with a value, because the assignment makes total local. A version that works takes the value as a parameter and returns the new one:

Python · editable

The output is 15.

  1. Write a function make_adder(n) that returns a new function. The new function takes one number and returns that number plus n. Create add_three = make_adder(3) and print add_three(4).

Show the answer

Python · editable

The output is 7. The inner function remembers n from the call that made it. It only reads n, so nonlocal is not needed.

  1. Explain in plain words why two functions can each have a local called x, but a loop that writes a name still leaves that name behind after it ends.

Show the answer

Each function call has its own set of local names, so the two x names never meet. A loop does not start a new set of names. It writes into the set of the function it is inside, so the name stays there after the loop finishes.

What you now know

  • A name made inside a function is local to that call, and it disappears when the call ends.
  • A for loop, if block or while loop does not create a scope. Names made inside them stay visible after they end.
  • Python looks up a name in this order: local, enclosing, global, then built-in.
  • A local name that hides a global name is shadowing. Inside the function, the global is not used.
  • To change a global inside a function, declare it with global. To change an enclosing function's name, use nonlocal.
  • A global name lasts for the whole run of the program. A local lasts for one call.
  • The messages to recognise are NameError: name ... is not defined, UnboundLocalError: cannot access local variable ... and SyntaxError: nonlocal declaration not allowed at module level.
Practice this topic: coding problems for Functions and Scope →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← One name, several inputs in Python