11B: Variable Scope

Lesson 11 has three parts A, B, C which can be completed in any order.

In this lesson we explain a concept called variable scope. The main idea is that it is possible to have two different variables with the same name whenever they have different scopes. This makes it much easier to write and edit programs, especially large ones. It is also a necessary ingredient in a later topic, recursion.

An example of variable scope

For this example, part of our program will be the following function:

def square(x):
   value = x * x
   return value
This is a function which computes the square of the number x, for example square(5) returns 25. Now suppose we call square from another part of the program which also uses a value variable for another purpose:

# main body of the program starts here
value = 17
fivesquared = square(5)
print(fivesquared, value)
The question is, what will be printed in this scenario? There are two possibilities, if we don't know how Python works:

  1. One possibility is that Python recognizes that the two value variables should be "kept separate" somehow. Thus, the call to square will return 25, but the value of value in the main body will stay at 17, and we see the output 25 17.
  2. The other possibility is that when square, executes, it over-writes the existing value of value with 25, and we see the output 25 25.

Let's see what actually happens!

We see that Python's behaviour is consistent with possibility #1. In fact, whenever you call a function, you cannot affect any variables defined outside the function. Instead, any statements that assign values to variables affect only a "local" variable which is "inside" the function call. The reason that Python, and most other languages, work like this is that it makes it much more simple to write long programs with many functions. In particular, when we define a new function, we are always allowed to use any variable names inside the function (including common ones like result, temp and i) even if they are being used somewhere else for different purposes.

To see this in detail, look at "Step 6 of 8" in the visualizer. Notice that there are two different value variables: one inside of the square function, and one outside (in the "global" area).

Multiple Choice Exercise: Inner and Outer
What is the output of the following program?

x = "outer"
def xReplace(value):
x = value
xReplace("inner")
print(x)
Correct! The statement x = value only affects the local version of variable x inside the function. The global version of x does not change. (So, the function xReplace is useless and never has any effect.)

Another similar concept to scope is a namespace; a namespace is like a scope for a package. So even if you import a package (such as math) which uses variable name x somewhere, it does not overlap with the variable x used by your program, since they lie in different namespaces.

Scoping Rules: Seeing Out

There are situations where we want to mix variables from local and global scopes. One common example is where you have some common variable initialized once at the start of the program, but you also want your functions to be able to read the variable.

Example
Reading a global variable from inside a function

Here, the local scope did not contain a variable named favouriteTopping, but this did not cause an error. Python's rule for evaluating variables is that, if a variable needs to be evaluated which does not exist in the local scope, then it looks for it in the global scope. In our case, it indeed found favouriteTopping in the global scope. (In a more general case with one function body calling another function you would have three scopes; Python always checks the "localmost" first and goes one step at a time to the global scope until the variable is found.)

Two of the examples above, with orderPizzas and xReplace, are almost syntactically identical. Why does Python create a new local variable x in xReplace, but no new favouriteTopping in orderPizzas? Python's rule is the following: if you only read from the variable, then no new local variable is created; but if you write to the variable even once, then the variable is treated as local throughout the function body. (This is the most common cause of UnboundLocalError: see these two questions [1, 2] in the official Python documentation.)

Function arguments are always treated as new local variables, as the following broken code fragment illustrates (it attempts to reset the variable g to 0, but fails).

global Changes

Like many other things, the normal flow described above works for 99% of all situations. But that remaining 1% of the time, you really might want to change a global variable from within a function. Python allows you to do this using the global statement.

Here's a modification of the earlier xReplace example. Note that we declaring the variable as global in the function. This lets you change the global value from within the function.

Reiterating: when inside of a function, assigning to a variable name defined at global scope actually creates a separate new local-scope variable with the same name, instead of modifying the global-scope variable (like resetToZero). If you want to update the global-scope variable, you must include the statement global «variable-name» inside of the function (like the fixed xReplace example). Then Python understands that changes to «variable-name» in the function are meant to refer to the existing global-scope variable.

As we mentioned earlier, reading a global variable does not require using the global statement; only writing. That concludes the lesson!