CS 1110 - Introduction to Programming

Starting Out with Python
Chapter 5, Functions, part 1

Objectives:

This lesson covers the first half of chapter 5, which introduces creation of functions. Objectives important to this lesson:

  1. Introduction to functions
  2. Void functions
  3. Designing a program with functions in mind
  4. Local variables
  5. Passing arguments
  6. Global variables and constants
  7. Value-returning functions
Concepts:
Chapter 5, part 1

Chapter 5 begins with a definition of what a function is. This could have come in any of the chapters so far, since we have been using built-in functions in all of them. Let's look at at the definition.

A function is a group of statements that perform a specific task. I have shortened the book's definition, because we have already seen that some functions do not "exist within a program". They can exist outside your program, and still be used in your program. We will explore how that happens as we go along.

In figure 5-1, the text shows us two approaches to writing a program. We could write the program as a long series of steps. We already know how to modify that approach by using loops and selection, but lets grant that a program with selection structures and loops still kind of flows straight down the page, only with some jumps and some loops.

The second approach takes us to a new idea. Imagine the payroll program with more features, such as calculating tax and with withheld amounts for benefits. What if we had a separate function for each of the features: one to collect employee ID, another to collect hours, another to calculate regular gross pay, another to calculate overtime (which would only run when there is overtime), another to calculate federal tax, another for state tax (which would vary by the states where our employees work), and more. There would have to be logic in the program that decides which functions we need to use for the employee whose data is being processed. This approach is modularized programming. We can create the separate functions we need, and only run the ones that apply to the current job we are doing.

We get a sales pitch on the next page that explains benefits of using a modularized approach:

  • simpler code - writing and troubleshooting each function can be simpler than trying to find a problem in a long, complex program
  • code reuse - having functions in one program that can be copied and pasted into another is a time saver, as long as the copied code is what we need, or is close enough that it can be easily modified
  • better testing - some of the programs we have written so far could have been easier to test if we used a modular approach, testing each module as we wrote it; this can be done with standard programming, but it is more difficult
  • faster development and facilitation of teamwork - if a team of five, for example, is working on a set of five programs that all need to perform some similar tasks, it makes more sense to assign writing the tasks to various team members, who could then share their code with each other, then the team members could proceed separately on the parts of the programs that are different from each other

The next section of the text does not explain its concept well enough. You already know that we can pass an argument to some functions. We have done so with every print() function we have used so far. You need to know that functions can also return things to the line of code that calls them. Not all functions return anything. Those that do not are called void functions. Those that do are called value-return functions. They have to be written so they know what value they return, which can be any type of data: int, float, string, or other types. We will begin by writing new void functions.

Void Functions

When we create a function, we begin by creating its definition, which is really writing the code it will use and giving it a name. Functions have to be defined before they are called, and they have to have a name to call them by. The text lists several rules about naming functions in section 5.2.

  • Don't use a keyword as a function name.
  • Don't use spaces in a function name.
  • Like a variable name, the first character of a function name must be a letter (upper or lower case) or an underscore. The rest of the name can be a combination of letters, underscores, and numerals.
  • Python cares about the capitalization you use in function names. (That's not a surprise, is it?)
  • This is a guideline, not a rule. Give your functions meaningful names, or you are likely to forget what they are supposed to do.

When you create a function, you need its name as the second thing you type. The first thing you type is the keyword def, which stands for definition. A function definition in Python will look like this:

def function_name():
    statement
    statement
    statement

This looks like other code structures we have used. There is a colon, and there are statements that are indented. That first line, the one that is not indented, is the function header. It has four parts that are all required: the keyword def, the function name, the set of parentheses, and the colon. The only part you have any say about, at this time, is the function name. (More secrets are coming...)

The text gives us an example of a new function that prints two lines. To run that function, or any function, we need a code statement that uses the function name like a command, like this:

function_name()

This is a function call. When we call a function, we have to include the parentheses as part of its name, but not the colon that we used in the definition. If the function expects to receive an argument, it goes inside the parentheses, just as you have always done when you called the print() function.

Figure 5-2 shows us how a program having this function could work. It has two comments at the top of the program file, followed by the function definition. There is another comment, then the line that calls the function. It is important to understand that when this program runs, the first thing that happens is that the definition of the function is loaded into memory, so it can be called. The first line that is actually executed in this program is the one that calls the message() function.

Modularized Programming

The next example is Program 5-2, which introduces another refinement. This program adds a new function to our example, called the main() function. In a modularized program, the main() function is the control center for your program. All program flow should happen in the main() function, which calls any other functions in the program as needed. If this were a program written in C, the main() function would run automatically when the program runs. Note that in the Python code examples in this part of the chapter, the main() function has to be called by a separate line of code, shown as the last line in each of these examples.

The text changes gears to discuss flowcharting a program with functions. In figure 5-9, the text shows us a flowchart for the program we have been discussing. Each of the two functions is charted separately, and the main() function contains a call to the message() function. On the next page, we see a hierarchy chart, which shows a different idea. It shows all the functions that exist in a program, linked in a vertical hierarchy. The links indicate that the functions in lower rows are called by the functions that link to them from above. In the illustration, the main() calls five other functions. Two of those five each call two other functions. The other three functions called by main() do not call other functions. Note that the hierarchy chart only shows the calls-called-by relationships. It does not show any code.

In section 5.4, the text tells us that variable used inside a function are usually local variables. This means that variable created inside a function are only available to that function, and are not available to the rest of the program. Program 5-4 is a program written by someone who does not understand this idea.

  1. In the main() function, the get_name() function is called.
  2. In the get_name function, the user is asked for their name, and it is saved in the variable called name.
  3. Back in the main() function, the print() function is called, and handed a literal and the variable called name. This is impossible, because the only variable called name exists only in the get_name function. The main() function has no access to this variable.
  4. There are solutions for this, but we won't handle this problem yet.
Local Variables

The text calls the part of a program in which a variable may be seen and used the variable's scope. If statements using that variable are in the same scope, the statements will work. If they are not in the same scope, the statements can't work. The text points out that even if statements using the variable are in the same scope, they won't work if the variable has not been created yet when the computer tries to execute those statements.

The text points out that the same variable names can be used in different scopes without violating any rules. Each of those same named variables would exist in its own scope, and none would know the others existed. This is a thing that it is possible to do, but it is probably a bad idea. It is too easy to become confused about where a particular variable can be accessed.

So, if variables are local by default, we need a way for functions to receive arguments. You have already used some of those, but you have not seen what makes them work. Turn to section 5.5 to learn an answer to this problem.

Passing Arguments

When we define a function, we don't have to leave the mandatory parentheses empty. Putting a local variable name inside the parentheses creates a function that can receive an argument. Such a variable is called a parameter. The text illustrates the concept with an example:

def show_double(number):
    result = number * 2
    print(result)

In this example, the function show_double() is created, and it expects to receive a value that it will store in the parameter variable called number. Inside the function, a new variable called result is created and assigned the value of number times 2. The function then prints the value that is stored in result. Read through program 5-6 to see an example using this method.

Like any other variable created and used inside a function, a parameter variable is limited in scope to the function that uses it. Some students get confused at this point. They start believing that the parameter variable exists in both the calling function and the called function. It only exists in the called function. The calling function passes a value (which may be stored in its own variable) to the called function. The name of the parameter variable that the called function uses to catch the value is unknown and unimportant to the calling function.

Well, programmers liked passing values to parameter variables so much that someone had to develop a way to pass multiple values at once. Program 5-8 illustrates how to do this with two literal values. It simply passes two values separated by a comma and a space. The receiving function has two parameters in its parentheses, separated by a comma and a space. It should be obvious that you can pass as many values as you want, as long as the called function has a matching set of parameters with which to receive those values. You can pass literal values, or the values held in variables. Just be aware that if you put the name of a variable in your argument list, you are only passing what that variable currently holds, not the variable itself.

We are almost halfway through the chapter, so hang on a bit longer.

Program 5-11 shows us something interesting. When we pass a value to a function, the function has to catch it in a variable. As the programmer, you are allowed to know the name of that variable, which is local to the function receiving the value. Why did I mention that? Watch what the author does in this one:

# first the author calls a function called show_interest, and he specifies what value is passed to which parameter variable
show_interest(rate=0.01, periods=10, principal=10000.0)

# the first line of the definition of the show_interest() function looks like this:
def show_interest(principal, rate, periods):

The author could have passed the three values to the called function in the order the function expected to receive them, eliminating the need to specify parameter variable names. Why do it this way?

  • This method makes it easier to troubleshoot because it makes explicit assignments to the parameter variables the called function uses.
  • This method can be used by a team of programmers when the called function has not yet been written, but has been designed, allowing the completed function to work in the program as long as its programmer does not deviate from the plan.

So what was remarkable about that? The calling function used the names of local variables in the called function. That should strike you as something that should not work. Oddly enough, it does work. You might think of it as referencing a variable that exists elsewhere, kind of like putting an address on an envelope before you mail it. The variable name is never actually used in the calling function. It is only used by the called function for delivery of data to its variables.

Global Variables and Constants

Okay, so now that we have you worried about local variables, let's see what we can do when local variables just won't get the job done. We can create a global variable. In this case, global means "throughout the current program". A global variable is accessible to all functions that are part of your current program. There are two tricks to it, but you have been doing one of them all along in your assignments. Look at programs 5-13 and 5-14 in your text. In program 5-13, the trick is to create a variable, but don't create it inside a function. It is best to create it at the beginning of your program file, before you define any functions. That's what you were doing with variables when you didn't know about functions.

  • If you create a variable inside a function, it is a local variable.
  • If you create it in code that is not inside a function, it is a global variable.

The second trick is shown in program 5-14. This program does not create a variable, it references one that was created according to the instructions above, in the program itself, not in a function. It says to the function, this is a global variable, not one of our locals. Look for it outside the function, and use it.

def function_name()
global number

# global is the keyword, number is the variable name. this is how your function gets access to the global variable named number.

The text warns us that we should assign a value to the variable right away, which is what happens in the next line in program 5-14: the user is asked to enter a value. Then, the program calls a function that uses the value in the variable. Yes, the function call could have passed a value to the function, but then the function would have to be defined as receiving a value. This way is simpler, but dangerous. If all functions have access to a variable, they can all redefine what that variable holds.

As the text explains, this makes troubleshooting problems with that variable harder, and it makes functions that use such a variable unfit to be reused by other programs that don't have an identical variable.

After complaining about the drawbacks of global variables, the text tells us about global constants. In the example program 5-15, a variable is created in the body of the program, outside of any function, making it global. This variable is named in all capital letters, making it a constant by convention. A programmer understanding this convention will know NOT to make any changes to the value of this variable elsewhere in the program. This is the best approach we have in Python to creating a global constant. It is actually a global variable, but we should agree not to change its value.

Value-returning Functions

The next to last section we will discuss this week is section 5.7, about creating functions that return values. To illustrate the idea, we are introduced to a function that returns a value. When you call a function of this sort, you usually catch the returned value in a variable, so the value can be used in other statements in your program. The text reminds us that we have called several functions in our programs so far, and it reveals to us that all of those functions are part of the interpreter itself. There are other functions that we can use that are stored in other files called modules. To use a function in one of those files, our program must load the module with a simple command. The keyword import is followed by the name of the module to be loaded. The module we will use in this example is called random. It contains several functions that deal with random numbers.

The command we will use to load the module is:

import random

Lines like this one should be the first lines in your program, before you have a chance to use any functions, especially any functions included in the library modules that you are importing.

The text introduces a new notation that we will use to call functions in a loaded module. The text explains that the function we are about to call is not in the usual place the interpreter finds functions, so we have to tell it to look for a function called randint, which is in the random module. The code that illustrates this has more to it:

number = random.randint(1, 100)

Reading from left to right:

  • the first thing we see is a variable that will catch/hold/remember the result of the function we are about to call. the variable is called number
  • the second thing we see is an equal sign, which is just an assignment operator
  • the third thing we see is the name of the loaded module that contains the function we are going to use
  • the name of the module is immediately followed by a dot, which is followed by the name of the function. the text calls this dot notation. the simple version of this is that the function is a member of, a part of the module, so the dot notation tells the interpreter where to look, and what to look for
  • finally, we see a set of parentheses, containing the arguments we are passing to the randint() function. in this case, the arguments are 1 and 100, meaning that we want the function to pick a number at random from the range 1 through 100

That's a lot of cargo for one little example. In program 5-16, we see the same notation used to select a number from 1 to 10, and in program 5-17, to select a series of random numbers in a loop. This brings up a question we should ask ourselves: how do we know where a loop ends in a Python program? We look for the indentation to change from right to left. Python loves indentation.

The text spends good bit of time on random number functions. That material will be needed at the end of next week, but it is not needed right now, so let's move on to section 5.8 for a quick introduction to creating your own value-returning functions. Defining a function that does a return only requires one more line of code, We can do it like this:

def function_name():
    statement
    statement
    statement
    return expression

This looks like the example I gave you above, but on the last line we see the keyword return, followed by any legal expression you want returned, usually something that was calculated in the function, such as the text shows us in program 5-21, in which we see a function has been created that adds two numbers together and reports the result with a return command.

We can start off at this point next week. For this week's assignment, you only need the material we have hit so far in the chapter.

 

Assignments

Assignments for these chapters will be found in Blackboard. We will explore that in class.