Scope of variables¶
This notebook covers roughly the same ground as this video.
Every variable in Python has a scope, which determines where the variable can be used and retains its meaning. Most of the variables that we have seen so far have global scope: they can be used and retain their value in most places. However, if we introduce a variable as part of a function definition, then it scope will be restricted to that function.
In the example below, x has global scope: we can use it both inside and outside the function f(a). However, the scope of b is local to the function f(a); we cannot use it outside the function, so the last line below produces an error message. Specifically, it produces a NameError, which is the standard thing when we try to refer to a variable that does not exist. Although the error here is easy to understand, you should read the error message carefully to start learning about how to interpret such messages.
x = 111 # This has global scope
print(f'Outside the function, x={x}')
def f(a):
b = 2 * a # Here we introduce a new variable with local scope
print(f'Inside the function, b={b}')
print(f'Inside the function, x={x}')
return 3 * b
f(100)
print(f'Outside the function, we still have x={x}')
print(f'Outside the function, b={b}') # This will fail
Here is a more complicated example where we have a global variable called x and a local variable of the same name. Inside the function, x refers to the local variable; outside the function, x refers to the global variable.
x = 111111 # This has global scope
print(f'Outside the function, x={x}')
def f(a):
# Here we introduce a new variable with local scope, which shadows the global x
# The assignment x = 2 * a changes the value of the local variable x, but not the global one
x = 2 * a
print(f'Inside the function, x={x}')
return 3 * x
f(100)
print(f'Outside the function, we still have x={x}')
You might want to change the value of a global variable from within a function. This can be done by declaring the variable to be global in the function body. However, this is rarely a good idea. Especially in large projects with many interacting pieces, this practice usually leads to code that is hard to understand and test and debug.
x = 1234 # This has global scope
def multiply_x_by(a):
global x
print(f'The global value of x is currently {x}')
# Because x has been declared global, the next line will change the
# global value of x instead of introducing a new local variable x
x = a * x
print(f'The global value of x has now changed to {x}\n')
multiply_x_by(2)
multiply_x_by(1000)
print(f'Outside the function, we still have x={x}')
There is a further wrinkle involving mutable objects. Suppose that l is a global variable whose value is a list. Inside a function we could do something like l.append(42), to add 42 on the end of the list. This does not count as introducing a new local variable; it just mutates the list referred to by the global variable l (even without a global declaration). This is similar to the last example and is discouraged for the same reasons.
l = [1,2,3,4]
def add_the_answer_to_l():
l.append(42)
print(f'Originally : {l=}')
add_the_answer_to_l()
print(f'After the function call: {l=}')
As well as local and global variables, we also have function arguments. These mostly behave like local variables. However, if a function argument is a mutable object like a list, then the function can mutate it (and this is not discouraged). An example is given below.
def add_aardvark(x):
x.append('aardvark')
u = ['elephant', 'zebra']
print(f'Originally : {u=}')
add_aardvark(u)
print(f'After the function call: {u=}')