Understanding Name and Variable Scope

Variables and Parameters are Local

An assignment statement in a function creates a local variable for the variable on the left hand side of the assignment operator. It is called local because this variable only exists inside the function and you cannot use it outside. For example, consider again the square function:

Python 3.3
1def square(x):
2    y = x * x
3    return y
4
5z = square(10)
6print(y)
Step 1 of 7
line that has just executed

next line to execute

Visualized using Online Python Tutor by Philip Guo
Frames
Objects

(bad_local)

If you press the ‘last >>’ button you will see an error message. When we try to use y on line 6 (outside the function) Python looks for a global variable named y but does not find one. This results in the error: Name Error: 'y' is not defined.

The variable y only exists while the function is being executed — we call this its lifetime. When the execution of the function terminates (returns), the local variables are destroyed. Codelens helps you visualize this because the local variables disappear after the function returns. Go back and step through the statements paying particular attention to the variables that are created when the function is called. Note when they are subsequently destroyed as the function returns.

Formal parameters are also local and act like local variables. For example, the lifetime of x begins when square is called, and its lifetime ends when the function completes its execution.

So it is not possible for a function to set some local variable to a value, complete its execution, and then when it is called again next time, recover the local variable. Each call of the function creates new local variables, and their lifetimes expire when the function returns to the caller.

On the other hand, it is legal for a function to access a global variable. However, this is considered bad form by nearly all programmers and should be avoided. Look at the following, nonsensical variation of the square function.

 
1
def badsquare(x):
2
    y = x ** power
3
    return y
4
5
power = 2
6
result = badsquare(10)
7
print(result)
8

(badsquare_1)

Although the badsquare function works, it is silly and poorly written. We have done it here to illustrate an important rule about how variables are looked up in Python. First, Python looks at the variables that are defined as local variables in the function. We call this the local scope. If the variable name is not found in the local scope, then Python looks at the global variables, or global scope. This is exactly the case illustrated in the code above. power is not found locally in badsquare but it does exist globally. The appropriate way to write this function would be to pass power as a parameter. For practice, you should rewrite the badsquare example to have a second parameter called power.

There is another variation on this theme of local versus global variables. Assignment statements in the local function cannot change variables defined outside the function. Consider the following codelens example:

Python 3.3
1def powerof(x, p):
2    power = p   # Another dumb mistake
3    y = x ** power
4    return y
5
6power = 3
7result = powerof(10, 2)
8print(result)
Step 1 of 9
line that has just executed

next line to execute

Visualized using Online Python Tutor by Philip Guo
Frames
Objects

(cl_powerof_bad)

Now step through the code. What do you notice about the values of variable power in the local scope compared to the variable power in the global scope?

The value of power in the local scope was different than the global scope. That is because in this example power was used on the left hand side of the assignment statement power = p. When a variable name is used on the left hand side of an assignment statement Python creates a local variable. When a local variable has the same name as a global variable we say that the local shadows the global. A shadow means that the global variable cannot be accessed by Python because the local variable will be found first. This is another good reason not to use global variables. As you can see, it makes your code confusing and difficult to understand.

To cement all of these ideas even further lets look at one final example. Inside the square function we are going to make an assignment to the parameter x There’s no good reason to do this other than to emphasize the fact that the parameter x is a local variable. If you step through the example in codelens you will see that although x is 0 in the local variables for square, the x in the global scope remains 2. This is confusing to many beginning programmers who think that an assignment to a formal parameter will cause a change to the value of the variable that was used as the actual parameter, especially when the two share the same name. But this example demonstrates that that is clearly not how Python operates.

Python 3.3
1def square(x):
2    y = x * x
3    x = 0       # assign a new value to the parameter x
4    return y
5
6x = 2
7z = square(x)
8print(z)
Step 1 of 9
line that has just executed

next line to execute

Visualized using Online Python Tutor by Philip Guo
Frames
Objects

(cl_change_parm)

Check your understanding

exceptions-4-16: What is a variable’s scope?




exceptions-4-17: What is a local variable?




exceptions-4-18: Can you use the same name for a local variable as a global variable?




The Scope of a Name or Variable

TODO

Functions can Call Other Functions

It is important to understand that each of the functions we write can be used and called from other functions we write. This is one of the most important ways that computer scientists take a large problem and break it down into a group of smaller problems. This process of breaking a problem into smaller subproblems is called functional decomposition.

Here’s a simple example of functional decomposition using two functions. The first function called square simply computes the square of a given number. The second function called sum_of_squares makes use of square to compute the sum of three numbers that have been squared.

Python 3.3
1def square(x):
2    y = x * x
3    return y
4
5def sum_of_squares(x, y, z):
6    a = square(x)
7    b = square(y)
8    c = square(z)
9
10    return a + b + c
11
12a = -5
13b = 2
14c = 10
15result = sum_of_squares(a, b, c)
16print(result)
Step 1 of 25
line that has just executed

next line to execute

Visualized using Online Python Tutor by Philip Guo
Frames
Objects

(sumofsquares)

Even though this is a pretty simple idea, in practice this example illustrates many very important Python concepts, including local and global variables along with parameter passing. Note that when you step through this example, codelens bolds line 1 and line 5 as the functions are defined. The body of square is not executed until it is called from the sum_of_squares function for the first time on line 6. Also notice that when square is called there are two groups of local variables, one for square and one for sum_of_squares. As you step through you will notice that x, and y are local variables in both functions and may even have different values. This illustrates that even though they are named the same, they are in fact, very different.

Now we will look at another example that uses two functions. This example illustrates an important computer science problem solving technique called generalization. Assume we want to write a function to draw a square. The generalization step is to realize that a square is just a special kind of rectangle.

To draw a rectangle we need to be able to call a function with different arguments for width and height. Unlike the case of the square, we cannot repeat the same thing 4 times, because the four sides are not equal. However, it is the case that drawing the bottom and right sides are the same sequence as drawing the top and left sides. So we eventually come up with this rather nice code that can draw a rectangle.

def drawRectangle(t, w, h):
    """Get turtle t to draw a rectangle of width w and height h."""
    for i in range(2):
        t.forward(w)
        t.left(90)
        t.forward(h)
        t.left(90)

The parameter names are deliberately chosen as single letters to ensure they’re not misunderstood. In real programs, once you’ve had more experience, we will insist on better variable names than this. The point is that the program doesn’t “understand” that you’re drawing a rectangle or that the parameters represent the width and the height. Concepts like rectangle, width, and height are meaningful for humans. They are not concepts that the program or the computer understands.

Thinking like a computer scientist involves looking for patterns and relationships. In the code above, we’ve done that to some extent. We did not just draw four sides. Instead, we spotted that we could draw the rectangle as two halves and used a loop to repeat that pattern twice.

But now we might spot that a square is a special kind of rectangle. A square simply uses the same value for both the height and the width. We already have a function that draws a rectangle, so we can use that to draw our square.

def drawSquare(tx, sz):        # a new version of drawSquare
    drawRectangle(tx, sz, sz)

Here is the entire example with the necessary set up code.

22
 
1
import turtle
2
3
def drawRectangle(t, w, h):
4
    """Get turtle t to draw a rectangle of width w and height h."""
5
    for i in range(2):
6
        t.forward(w)
7
        t.left(90)
8
        t.forward(h)
9
        t.left(90)
10
11
def drawSquare(tx, sz):        # a new version of drawSquare
12
    drawRectangle(tx, sz, sz)
13
14
wn = turtle.Screen()             # Set up the window
15
wn.bgcolor("lightgreen")
16
17
tess = turtle.Turtle()           # create tess
18
19
drawSquare(tess, 50)
20
21
wn.exitonclick()
22

(ch04_3)

There are some points worth noting here:

  • Functions can call other functions.
  • Rewriting drawSquare like this captures the relationship that we’ve spotted.
  • A caller of this function might say drawSquare(tess, 50). The parameters of this function, tx and sz, are assigned the values of the tess object, and the integer 50 respectively.
  • In the body of the function, tz and sz are just like any other variable.
  • When the call is made to drawRectangle, the values in variables tx and sz are fetched first, then the call happens. So as we enter the top of function drawRectangle, its variable t is assigned the tess object, and w and h in that function are both given the value 50.

So far, it may not be clear why it is worth the trouble to create all of these new functions. Actually, there are a lot of reasons, but this example demonstrates two:

  1. Creating a new function gives you an opportunity to name a group of statements. Functions can simplify a program by hiding a complex computation behind a single command. The function (including its name) can capture your mental chunking, or abstraction, of the problem.
  2. Creating a new function can make a program smaller by eliminating repetitive code.
  3. Sometimes you can write functions that allow you to solve a specific problem using a more general solution.

Lab

  • Drawing a Circle In this guided lab exercise we will work through a simple problem solving exercise related to drawing a circle with the turtle.

Flow of Execution Summary

When you are working with functions it is really important to know the order in which statements are executed. This is called the flow of execution and we’ve already talked about it a number of times in this chapter.

Execution always begins at the first statement of the program. Statements are executed one at a time, in order, from top to bottom. Function definitions do not alter the flow of execution of the program, but remember that statements inside the function are not executed until the function is called. Function calls are like a detour in the flow of execution. Instead of going to the next statement, the flow jumps to the first line of the called function, executes all the statements there, and then comes back to pick up where it left off.

That sounds simple enough, until you remember that one function can call another. While in the middle of one function, the program might have to execute the statements in another function. But while executing that new function, the program might have to execute yet another function!

Fortunately, Python is adept at keeping track of where it is, so each time a function completes, the program picks up where it left off in the function that called it. When it gets to the end of the program, it terminates.

What’s the moral of this sordid tale? When you read a program, don’t read from top to bottom. Instead, follow the flow of execution. This means that you will read the def statements as you are scanning from top to bottom, but you should skip the body of the function until you reach a point where that function is called.

Check your understanding

exceptions-4-19: Consider the following Python code. Note that line numbers are included on the left.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def pow(b, p):
    y = b ** p
    return y

def square(x):
    a = pow(x, 2)
    return a

n = 5
result = square(n)
print(result)

Which of the following best reflects the order in which these lines of code are processed in Python?







exceptions-4-20: Consider the following Python code. Note that line numbers are included on the left.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def pow(b, p):
    y = b ** p
    return y

def square(x):
    a = pow(x, 2)
    return a

n = 5
result = square(n)
print(result)

What does this function print?