indusai.co

Scope

Scope decides which variables are visible from a given line of code. Getting scope right avoids a whole class of confusing bugs.

Local scope

A variable created inside a function exists only inside that function.

python
def demo():
    inner = "I live inside demo"
    print(inner)

demo()
try:
    print(inner)
except NameError as e:
    print("Error:", e)

Each call gets fresh local variables. When the function returns, they are gone.

Global scope

A variable created at the top level of a file is global. Functions can read it.

python
rate = 18

def with_tax(amount):
    return amount * (1 + rate / 100)

print(with_tax(100))

Assigning creates a new local

If you assign to a name inside a function, Python treats it as a new local variable, even if a global with the same name exists.

python
count = 0

def increment():
    count = 1     # this is a new local, the global is untouched
    print("inside:", count)

increment()
print("outside:", count)

The global keyword

To modify a global from inside a function you must declare it.

python
count = 0

def increment():
    global count
    count += 1

increment()
increment()
print(count)

Use this sparingly. Functions that silently change globals are hard to reason about. Prefer returning a value and assigning it outside.

Enclosing scope and nonlocal

Functions can be defined inside other functions. The inner function can read the outer function's variables. To assign to them, use nonlocal.

python
def counter():
    n = 0
    def tick():
        nonlocal n
        n += 1
        return n
    return tick

c = counter()
print(c(), c(), c())

This pattern is called a closure: tick remembers the n from the call to counter that created it.

The LEGB rule

When Python sees a name, it looks in this order:

  1. Local: the current function
  2. Enclosing: any outer functions
  3. Global: the module
  4. Built in: names like print, len, range
python
def outer():
    x = "enclosing"
    def inner():
        print(x)
    inner()

x = "global"
outer()

Shadowing built ins

You can name a variable list or sum, but then the built in is hidden for the rest of that scope. Editors will warn you; listen to them.

python
def demo():
    sum = 10          # shadows the built in sum()
    print(sum)
demo()
print(sum([1, 2, 3]))  # the built in is still fine out here

Practice

  1. Write a function that tries to print a variable defined only inside a different function. Read the error.
  2. Write a make_greeter(greeting) function that returns an inner function which greets any name with that greeting.
  3. Fix this code so it works without global: a function that "increments" a score, by returning the new score instead.