Skip to content

Scope: where names live

~15 min ยท prerequisite: Defining and calling functions

What you will be able to do

  • Explain the difference between a local variable and a global variable.
  • Predict whether a name can be read inside a function, and whether a function changes it.
  • Pass values into a function and get results back, instead of changing global names.

The idea

The scope of a name is the part of a program where that name can be used. Scope answers one question: when Python sees a name, where does it look for the value?

Think of a note written inside a room: it can be read only in that room. In Python, a name created inside a function is a local variable. It exists only while that function is running. A name created at the top level of a file is a global variable. It can be read from anywhere in the file.

A function can read a global variable without any special words. This is why a function can use a setting, such as a tax rate, that is defined once at the top of the file. When Python sees total inside a function, and that function does not create a local total, it looks outward and finds the global one.

Assigning to a name inside a function is different. The assignment total = 5 inside a function creates a new local variable called total. The global total is not touched. The two names only share a spelling.

Python checks names in a fixed order: first the local scope, then any enclosing function scopes, then the global scope, and finally the built-in names such as print and len. This order explains why a local name hides a global name with the same spelling.

If you really want a function to change a global variable, you must say so with the keyword global. It is usually better to pass values in as arguments and return results out.

Worked example

total = 100


def add_bonus(amount):
    total = amount + 5
    return total


result = add_bonus(10)
print(result)
print(total)

Expected output:

15
100

Line by line, this is what Python does:

  1. total = 100 creates a global variable named total.
  2. def add_bonus(amount): creates a function. Its body does not run yet.
  3. Inside the function, total = amount + 5 creates a local variable named total. Because amount is 10, the local total holds 15.
  4. return total returns the local value, 15. The function then ends, and its local total disappears.
  5. result = add_bonus(10) stores 15 in result, and print(result) shows 15.
  6. print(total) looks at the global name. It was never changed, so it shows 100.

Try it

The first block shows that a function can read a global name. The second block shows that a local assignment does not change the global. The third block uses global, and then passes a value in instead.

count = 0


def read_count():
    return count


assert read_count() == 0
score = 1


def change_score():
    score = 50
    return score


assert change_score() == 50
assert score == 1
counter = 0


def bump():
    global counter
    counter += 1


bump()
bump()
assert counter == 2


def add_one(value):
    return value + 1


assert add_one(counter) == 3

Common mistakes

  1. Changing a global without global. The following code tries to add to total inside the function:

    total = 0
    
    
    def update():
        total = total + 1
        return total
    
    
    update()
    

    Python reports:

    UnboundLocalError: cannot access local variable 'total' where it is not associated with a value
    

    The assignment makes total a local name, so the read on the right side has no value yet. Either add global total or return the new value.

  2. Using a local name outside its function. The following code reads text after the function has ended:

    def make_message():
        text = "hi"
    
    
    make_message()
    print(text)
    

    Python reports a NameError that begins with:

    NameError: name 'text' is not defined
    

    Depending on the Python version, the message may end with a suggested name, such as Did you mean.

    The variable text existed only while make_message was running. Return it, and store the result in a variable.

  3. Expecting a local change to reach the global. The following check fails:

    level = 1
    
    
    def raise_level():
        level = 2
    
    
    raise_level()
    assert level == 2
    

    Python reports:

    AssertionError
    

    The function assigned a new local level. The global level is still 1.

  4. Putting global after the name is used. The following code reads counter before the global line:

    counter = 0
    
    
    def show():
        print(counter)
        global counter
    

    Python reports:

    SyntaxError: name 'counter' is used prior to global declaration
    

    Put the global line first in the function body.

Practice

  1. Define a global x = 5, then define plus_one() that returns x + 1. Assert that plus_one() equals 6.
  2. Define a global user = "Ada" and a function get_user() that returns it. Assert that get_user() equals "Ada".
  3. Define a global points = 10 and a function add_points(amount) that uses global points and adds amount. After add_points(5), assert that points equals 15.
  4. Define double_all(numbers) that returns a new list with each value doubled, and leaves the list it was given unchanged. Assert that double_all([1, 2]) equals [2, 4], and that the original list still equals [1, 2] after the call.
  5. Stretch: define make_counter() that creates a local count = 0 and returns a function step(). Inside step, use nonlocal count to add 1, then return the new count. Assert that the first counter returns 1 then 2, and that a second counter starts again at 1.
Solution
  1. ```python x = 5

def plus_one(): return x + 1

assert plus_one() == 6 ```

  1. ```python user = "Ada"

def get_user(): return user

assert get_user() == "Ada" ```

  1. ```python points = 10

def add_points(amount): global points points = points + amount

add_points(5) assert points == 15 ```

  1. ```python def double_all(numbers): doubled = [number * 2 for number in numbers] return doubled

original = [1, 2] assert double_all(original) == [2, 4] assert original == [1, 2] ```

  1. ```python def make_counter(): count = 0

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

    return step

first = make_counter() assert first() == 1 assert first() == 2

second = make_counter() assert second() == 1 ```

Recap

  • A name assigned inside a function is local to that call. Reading a global name from inside a function works.
  • global lets a function assign to a top-level name, but returning a value is usually clearer.
  • A local variable is created on each call and disappears when the function returns.

Sources