Post

Variable scope and good variable names in Python

Where a variable can be seen and changed in Python: local, global and nonlocal scope, the global and nonlocal keywords, shadowing, and how to choose clear names that follow PEP 8.

The scope of a variable is the part of the program where its name can be used. It is the reason a variable can be “missing” in one function and work fine in another.

Every example ran with Python 3.13, checked against the official tutorial on scopes, the global statement reference, and PEP 8.

Where Python looks for a name

When you use a name, Python searches in this order: the current function (local), then any enclosing functions, then the module (global), and finally the built-in names such as print and len. The docs describe it the same way: “the innermost scope, which is searched first, contains the local names”.

x = "global"

def show_local():
    x = "local"
    print("inside:", x)

show_local()
print("outside:", x)
inside: local
outside: global

Inside the function, x = "local" creates a new local variable and leaves the global x alone. The docs say that without a global or nonlocal statement, “assignments to names always go into the innermost scope”.

Reading a global is fine

A function can read a global variable without any keyword:

def read_only():
    print("read:", x)

read_only()
read: global

Only assigning to the name inside the function makes it local.

The global keyword

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

def with_global():
    global x
    x = "changed by global"

with_global()
print("now:", x)
now: changed by global

The docs say a declared name is “interpreted as globals”, and all assignments go to the module-level namespace. Use it sparingly: functions that change global state are harder to test and follow, and passing the value in and returning the new one is usually better.

The nonlocal keyword

For a function inside another function, nonlocal changes a variable of the enclosing function:

def outer():
    y = "outer"
    def inner():
        nonlocal y
        y = "changed by nonlocal"
    inner()
    print("outer y:", y)

outer()
outer y: changed by nonlocal

Without nonlocal, inner would create its own y, and outer would print outer.

A name must be declared before use

Inside a function, a variable is local for the whole function body. If you use it before the assignment, you get an error:

def bad_count():
    print(total)
    total = 5

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

total is local because of the assignment further down, so the print above looks for a local value that doesn’t exist yet. The same rule applies to global: it must come before any use of the name in that function. Otherwise you get a SyntaxError:

counter = 0

def bump():
    counter += 1
    global counter
SyntaxError: name 'counter' is assigned to before global declaration

Shadowing a built-in

Scope also explains what happens when you use a built-in name for your own variable:

print(len("abc"))
len = 3
print(len)
3
3

After len = 3, the module-level len hides the built-in len function. Any later call to len(...) in this file fails. This is called shadowing.

Choosing good names

PEP 8 gives the rules for names. The main ones:

  • Variables and functions: lowercase, with underscores. total_price, not totalPrice or TP.
  • Constants (values that don’t change, usually at module level): all capitals. MAX_RETRIES = 3.
  • Avoid l, O and I. The docs for PEP 8 say these look like the digits one and zero in some fonts.
  • Avoid built-in names. list, str, sum, max, id and len are common mistakes.

Compare these two versions of the same code:

def f(l, d):
    return l * d

def calculate_total_price(unit_price, quantity):
    return unit_price * quantity

The second tells the next reader what the numbers mean, which is the whole point of a name. d could be a date, a dictionary or a dog.

Python also has reserved words, which you can’t use as names. class is one of them:

import keyword
print(keyword.iskeyword("class"), keyword.iskeyword("name"))
True False

Trying class = 1 gives SyntaxError: invalid syntax.

What’s next

Next in the series: TypeError, the most common error when a value has the wrong type, and how to check types.