Functions, return values, and scope

Some functions return values

Expressions compute values and make those values available for further use. Functions can also compute values and make those values available for further use. They don’t have to do that, but they can. For example, the sqrt function returns a value, which is the square root of the value of the actual parameter passed to it. When a function computes a value that is available for further use, we say that the function returns the value.

Anywhere a value is needed, a call of a function that returns the right type of value (for example, an int, a float, a string, or a boolean) can appear.

For example, in a print statement, we can print the value returned by a function call:

Let’s dissect what is meant by “Anywhere a value is needed, a call of a function that returns the right type of value can appear.” Consider this line of code:

print( 8 + 12 )

Of course, this code outputs 20. Now, if we were to call int(8.48528137423857), the value returned by the call of int would be 8, so let’s change the code to

print( int(8.48528137423857) + 12 )

Do you see what I’ve done here? We needed an int value as the left operand of +. Instead of 8, we used int(8.48528137423857): a call to the int function, which returns the value 8. Why did I choose int(8.48528137423857)? Because I’m going to go a step further. The sqrt function, when passed the actual parameter 72, returns the float value 8.48528137423857. So we can change the code to

print( int(sqrt(72)) + 12 )

We needed the value 8.48528137423857, and instead we used the function call sqrt(72). Functions are called in the order that their values are needed to compute the expression. So in this example, sqrt(72) calls the sqrt function with the actual parameter 72. The result 8.48528137423857 is then made available. Then the result of calling int is needed, so the int function is called, passing in the value 8.48528137423857 as a parameter. int executes and returns the value 8, which is made available. Finally, 8 + 12 is computed.

Built-in functions: len, int, float, str

len returns the length of a string, as an int value.

As we’ve seen, int converts from some other type to an int. If the number is a float, any part of the number to the right of the decimal point is truncated (dropped). If the number is too large to store in an int, Python returns a long int instead.

If you try to convert from a string that contains characters that represent something other than a number, int will fail and your program will terminate. print(int("123")) will work. print( int("buffalo")) will not.

float and str convert to float and string types, respectively, and work as you might expect.

Functions that return random values

We can call the function randint from the random module to get (nearly) random values. For example:

This line prints a random integer between 5 and 20, inclusive. In other words, it’s equally likely to print any integer in the range from 5 to 20.

What if you want a random floating-point number? Call uniform, also from the random module:

Exercise: coin flip

Objective: Make use of different results of a function call to take different actions.

Write a loop that simulates flipping a coin 5 times, and prints out “heads” or “tails” after each flip. Use the Python randint function to determine if the outcome of each flip should be heads or tails.

Exercise: lots of smiles

Objective: Write a function that makes use of a different function you’ve written many times.

Write a function that draws n smiley faces of random size at random locations on the screen, where n is a parameter to the function. Click on the left arrow next to the draw function to unfold that function in the editor, and call your function to draw 20 random smiley faces. You may assume that the screen width and height are both 200. As a starting point, here is a function to draw a single smile. Don’t forget needed import statements.

Defining your own functions with return values

When the value returned by a function is needed, the function is called. The current value of the program counter is saved. Then the program counter is set to the first line of the function. Python executes the body of the function. The function returns and the program counter is set to its value before the function call when one of two things happens:

Executing a return-statement does two things:

  1. return returns the value following return (if any) to the calling code.
  2. return immediately stops execution of the body of the function and resumes execution at the point of call. That is, the program counter goes back to just after the function was called.

Here’s a really simple example.

As we now know, anywhere a value is needed, you can substitute an expression or function call that returns a value. For example, you could do something like this:

The line print( "I computed the value!" ) is not printed on the screen, since the return-statement before the print-statement will always immediately give control back to the calling function by setting the value of the program counter. In fact, Python will warn you that you have done something silly. Lines of code that cannot be reached are called dead code. Normally, you should not include dead code in a program.

Exercise: area 51

Objective: Write a function that returns a value.

First, write a function circle_area51 that computes the area of a circle of radius 51, and returns that area. Call the function and print the result to verify that it works.

Then write a function circle_area that takes a parameter radius, and computes the area o a circle with that radius. Call the function three times, to compute the areas of circles of size 3, 5, and 51, and print the results.

Local and global variables

Variables in Python are either local to a single function or global and accessible by any function. Python has some particular rules that determine whether a variable is local or global and how you access the variable.

Local variables

When the first time you assign to a variable is inside a function, that variable is a local variable. A local variable is not accessible by any code outside the function in which it is defined. A local variables exists during that call of the function, and then it ceases to exist. You can think of local variables as disposable—use them in the function, and throw them away.

In this example, the variable x is local to some_function. The line print( x ) within some_function prints 4.

But the line print( x ) after the call to some_function is an error. That’s because the first time that x is assigned is within some_function, and so x is local to some_function. Therefore, x is not known outside some_function.

Local variables:

  1. cannot be accessed outside of the function where they are created.
  2. are destroyed as the function returns.
  3. are used to store temporary computation results.

Formal parameters are local variables, too.

We say that a local variable is in scope inside the function where it is created, after assignment.

Local variables are good. Most lines of code compute something, and store it in a nicely named local variable. Later lines of code use that variable to compute something else, and store it in a nice local. Using well-named local variables for intermediate computation is as important as commenting to make code understandable and modifiable by human readers.

Function frames and scope

The values of variables are stored in memory. Where? Each function call creates an area of memory called a frame to store values of the local variables that it creates. When the function is exited, the frame is destroyed, and the local variables are no longer in scope. This is good: the function is cleaning up after itself.

Global variables

Every now and then, you want a variable to be available to many functions. Such a variable cannot be local, because a local variable is accessible only within the function in which it’s first assigned to.

A variable that is accessible by many functions is a global variable. Useful though may be, global variables also have their seamy underbelly, and so you should use them only when necessary.

Global variables are initialized outside of function definitions. If you are simply using the value of a global variable, you can access that global anywhere in any line of code (within the same file) that runs after the initialization of the variable.

Changing the value of a global variable is a big deal. Why? Doing so may affect countless other functions, some of which you might not even know about or have written. Where possible, you should avoid changing global variables, and Python forces you to explicitly tell it that the variable is global before using any assignment statements.

In any function where you want to assign to a global variable, you must use the global keyword to indicate that you really do intend to change the value of the global variable. Here’s an example.

Here, the function print_x accesses the global variable x. Because x has the value 5 when the function is called, the assignment statement within the function assigns the value 6 to x, and both print statements print the value 6.

It’s important to remember that the keyword global is not needed to create a global variable. If you just intend to make use of the value, you should not use the keyword global, since the keyword makes it possible to change the value, and this could affect any code that depends on the global variable.

What’s wrong with global variables? There are two obvious ways to get values into a function. The first is to use pass actual parameters into formal parameters; the second is to set a global variable that the function can use. If you choose the second, then

  1. It is not obvious from reading the function header what values the function requires and makes use of.
  2. If you change the value of the global for some other reason that has nothing to do with that function, the function will behave differently, and that’s hard to predict or debug, since you may have forgotten all about this function in the meantime.
  3. You will add more variable names to the global frame. There is a single global frame used to store the values of global variable values; you need to find a unique variable name for each new global variable.

Although we’ll use some global variables for demonstration purposes, and there are even a few occasionally good uses of global variables, using a global variable should always make you very uncomfortable at the least. Frequently, you can wrap bare code inside a function, making variables local, and then pass the values of those variables into whichever function needs them.

Stylistically, using local variables and parameters to transfer data makes the flow of information clear throughout the program. Some function you want to use take some parameters; you think about what values you need and how to compute them. The function returns that value and you know you can use it. If the function uses or changes global values to get information in or out, it’s harder to see how to connect that function to other code.

Global variables can serve as named constants

There is one use of global variables that is fairly safe and, in fact, good programming practice: as named constants. For example, a chemistry program might use Avogadro’s number in several functions. If you typed the actual number in every time you used it in an equation, you might make a mistake, and it might be hard for a reader to figure out the equations, if they don’t recognize the number. A global variable can store the number, make it easy to change that number without searching and replacing, and makes the code more readable.

AVOGADRO = 6.0221415e23

We call global variables used to store values that don’t change constants. It’s good programming practice to type constants using all uppercase letters, so that they are recognizable as constants.

There are some built-in global variables in Python modules that you can use by importing them.

Notice that pi is not capitalized in this case. The capitalization is just a convention, and apparently the Python designers didn’t use that convention. Bad Guido! (That’s Guido van Rossum, the designer of Python, who is also known as The Benevolent Dictator For Life.)

Although a global variable such as pi imported from the math library is intended to be constant, Python doesn’t prevent you from changing it.

(I checked on the Internet about this story that the Great State of Kansas attempted to change the value of π to 3. Turns out it’s not true. Yay, Kansas!)

With great power comes great responsibility. Python lets you change the value of variables intended to serve as named constants. Don’t do it.

A style note about boolean values and expressions

Consider the following code:

The test x == 5 evaluates to a boolean expression, as must any test in an if-statement. This boolean expression must have either the value True or False, right? And if the boolean expression x == 5 has the value True, the function returns True. If the boolean expression x == 5 has the value False, the function returns False.

In other words, the function returns exactly the same value that the expression x == 5 evaluates to. So here’s another way to write this function, which is shorter and more direct:

How does it work? We evaluate the expression x == 5, which comes to either True or False, and we return exactly this boolean value. Notice that in either version of is_five, we return True precisely when x equals 5, and we return False precisely when x does not equal 5.

You should avoid code like the first version of the function. When you write code the first way, with an if-else-statement that just mirrors the value of a boolean expression, you are declaring to all the world “I don’t understand the boolean type!!!”