Hour 8. Using Functions to Create Reusable Code


What You’ll Learn in This Hour:

Image How to create a simple function

Image How to pass values to functions

Image How to create temporary variables in functions

Image How to contain several functions within a function

Image When to use functions in the real world


If programs were made of code that ran from the first line to the last line, they would be impossibly long. They’d take up more space on your hard drive, and they’d be almost impossible for a developer to maintain. In this hour, you’ll learn how to use functions to break up your code in a way that makes it shorter, easier to maintain and read, and easier to reuse. We’ve already been using functions such as len() and input() in earlier hours. Now, we’re going to build our own.

Creating a Basic Function

Most programs are made of smaller chunks of code that can be reused over and over. You can give these chunks information, which can change what is calculated, displayed, or stored. You may have a chunk that prints out a welcome message for a logged-in user. Another chunk may calculate the total of all the items in a list. We call these chunks of code functions.

Functions in Python need three things: a name, a block of code, and (optionally) some variables that hold what values you send the function (we call these parameters). The format looks like this:

def function_name(parameter1, parameter2)
  code
  code
  code
  code

To use a function, you have to call it. You do this by using the function name, followed by a pair of parentheses. Here, we have a function called hello2. When we call it, we get a generic greeting.

>>> def hello():
...   print "Hello! How are you?"
...
>>> hello()
Hello! How are you?

You can also send values to a function by “declaring parameters” for that function. Parameters are declared in the parentheses after the function name. Then, when you call the function, you can send those parameters values.

>>> def hello2(name):
...  print "Hello, {}".format(name)
...
>>> hello2('Hannah')
Hello, Hannah

If you don’t add the parentheses, Python doesn’t know that you actually want to call the function. Although the program won’t crash, you’ll get some strange output. Watch what happens when we call the function hello without using the parentheses:

>>> def hello():
...   print "Hello! How are you?"
...
>>> hello
<function hello at 0x10ccfccf8>

Rather than print out “Hello! How are you?”, Python printed out <function hello at 0x10ccfccf8>. Know that if you see something like this, you probably forgot to call a function.

Passing Values to Functions

So far, we’ve passed one value to one parameter. What if we want to pass more than one value? If you know specifically how many values you want to pass along, you can add more parameters when you define the function.

Here, we have two parameters for our function: name and address. When we call the function, we’re going to pass it two values.

>>> def print_address(name, address):
...   print name
...   print address
...
>>> hospital = "INOVA Hospital"
>>> address = "300 Prince William Parkway
... Woodbridge, VA 22193"
>>> print_address(hospital, address)
INOVA Hospital
300 Prince William Parkway
Woodbridge, VA 22193

If you pass in more than one value, Python will save the first value in the first parameter, the second value in the second parameter, and so on. This can get confusing, however. As your program grows, it becomes difficult to remember in what order you defined your parameters. This is why it’s usually recommended to tell Python which value goes with which parameter.

You do this by using the parameter’s name in the function call. Rather than sending in (value1, value2), you send in (parameter1=value1, parameter2 = value2). Here, we have a function with two parameters. This time, though, when we call it, we’re going to use the parameters’ names.

>>> def print_total(customer_name, items):
...   print "Total for {}".format(customer_name)
...   total = 0
...   for item in items:
...     total = total + item
...   print "${}".format(total)
...
>>> print_total(items=[4.52, 6.31, 5.00], customer_name="Karen")
Total for Karen
$15.83

Even though we passed in items before customer_name, Python saved the list and the string to the correct parameters. Had we not specified where the values were supposed to be saved, we would have gotten some rather strange output. Here’s the output we would have gotten had we not specified which value was for which parameter:

>>> print_total([4.52, 6.31, 5.00], "Karen")
Total for [4.52, 6.31, 5.0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in print_total
TypeError: unsupported operand type(s) for +: 'int' and 'str'

The list is saved as the items we wanted to add, and the string is saved as the user’s name. Although printing the string works, summing up the letters in "Karen" doesn’t work at all because total is initially set to 0, an integer.

Setting Default Values

One problem with sending values to a function is that Python will always expect some values to be sent. But what if we didn’t have a customer name? If we leave it out, we’re going to get an error:

>>> print_total(items=[1,5,8,3])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: print_total() takes exactly 2 arguments (1 given)

Sure, we could make up a fake value for customer_name, but it would be much more elegant if we could supply some sort of default value. That way, if a user leaves out a field, we know that the program will still be able to run. You add a default to a parameter by setting it to some value in the function definition (parameter='somevalue'). If another value is passed for the parameter when the function is called, the value passed is used instead of the default.

Here, we have a function that accepts a first, middle, and last name. Not everyone has a middle name (or wants to enter it), so we make the default value for middle an empty string.

>>> def print_welcome(first, last, middle=''):
...   print "Welcome, {} {} {}!".format(first, middle, last)
...   print "Enjoy your stay!"
...
>>> print_welcome(first = "James", last = "Grinnell")
Welcome, James  Grinnell!
Enjoy your stay!
>>> print_welcome(first = "Katie", middle = "Alison", last="Cunningham")
Welcome, Katie Alison Cunningham!
Enjoy your stay!

Why aren’t the parameters defined as (first, middle='', last)? One of the rules of setting defaults is that you define them last in the function definition. All the parameters that must be defined by whatever is calling the function should go first, so Python fills them first. After providing the parameters that have no defaults, you can start defining parameters that have defaults.

Although setting a default is nice, not every parameter in your function should have one. Consider if you really do want your program to stop if it doesn’t have the data it needs to run. If users are joining a mailing list, you wouldn’t want them to skip entering their email address, after all! Also, make sure your defaults are reasonable (usually, blank is best). You wouldn’t want to assume that a user is male or female based on them not answering what gender they are, nor would you want to guess at a phone number.

Returning Values

So far, we’ve been running blocks of code, then returning to the main program. Many times, though, we want the function to give us a value. For example, what if we want a function that totals up all the values in a list and then gives us that value so we can use it later? In this case, we use return.

Here, we have a function that totals up all the items in a list and then, using return, gives us that value, which we store in items_total:

>>> def get_total(items):
...   total = 0
...   for item in items:
...     total = total + item
...   return total
...
>>> items = [2,5,7,8,2]
>>> items_total = get_total(items)
>>> items_total
24

return can return more than one value. Just separate each return with a comma. Here, we return both the square and the cube of a given number:

>>> def get_square_and_cube(number):
...   square = number ** 2
...   cube = number ** 3
...   return square, cube
...
>>> result = get_square_and_cube(5)
>>> result
(25, 125)

Python will return a tuple that contains all the values returned by the function. Often, you’ll see developers skip that step and assign the values returned from the function to their own variables. Here, we save the values to two variables, square and cube:

>>> square, cube = get_square_and_cube(3)
>>> square
9
>>> cube
27

The trick is that you have to know how many items you’ll be getting back. If you try to set too many variables (or too few), you’ll get an error. If a function is expecting five variables, you need to give it exactly five variables. If it’s expecting none, then you need to send none.

Here, the function get_five_things returns five items. If we use too few or too many variables, though, we get an error:

>>> def get_five_things():
...   return 1, 2, 3, 4, 5
...
>>> a, b, c = get_five_things()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack
>>> a, b, c, d, e, f = get_five_things()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: need more than 5 values to unpack

Lastly, you don’t need to return a value when you use a return statement. Sometimes, you just want to go back to the main program rather than run the rest of the code in the function. Here, in check_year, we return to the main program if the year is four characters long:

>>> def check_year(year):
...   if len(year) != 4:
...     print "{} is invalid as a year.".format(year)
...     return
...   print "Good, that seems to work as a year!"
...
>>> check_year("80")
80 is invalid as a year.

Because "80" has a length of two, the function prints out an error statement and returns to the main program rather than telling the user that the value they entered is good.

Variables in Functions: Scope

Sometimes in functions, you’re going to create new variables to store data. A variable might be for a loop. It might be part of a long calculation. You might be getting input from the user. When you declare a variable in a function, it only exists within that function. That’s called scope.

Creating Variables Within Functions

When a variable is “in scope,” it can be referenced. You can see what it contains. You can change it. You can use its methods. The second it goes out of scope, however, you can’t reference it. It’s like the program forgets it ever existed.

Let’s play with scope. In the following example, we’re going to create a function where we’ll create a variable called name. What happens when we try to reference it outside of the function?

>>> def get_name():
...   name = raw_input("Give me your name: ")
...
>>> get_name()
Give me your name: Katie
>>> name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'name' is not defined

Once we left the function, name stopped existing, so we couldn’t see what the user had entered.

This may seem counterintuitive. What is the harm in keeping the variables around? This would not only chew up quite a bit of memory, but it would also make programs rather unwieldy.

Many times, you’ll need to use functions that you didn’t write. All you should be concerned about is what the function does, what you need to give it, and what you can expect back. The function shouldn’t muck around with the variables you’ve already defined, and it shouldn’t start defining new variables that might interfere with how your program runs after it’s called. This is called the “black box principle”: Functions are a black box. Data goes in, magic happens, data comes out. You shouldn’t have to worry about the magic.

Parameters and Scope

When you send values to a function, you’re usually sending copies of them. Even if you use a variable and that variable happens to be named the same as the parameter in the function, changes you make to the function will not change the values held in the original variable.

Here, we have a function called add_five. We pass in a number, add five to it, and then print the number. Note that we don’t return the number.

>>> def add_five(number):
...   number = number + 5
...   print number
...
>>> x = 5
>>> add_five(x)
10
>>> x
5

So, what happens to the original number? The original number, stored in x, isn’t changed after the function is called. Only a copy was sent to the function.

This isn’t always true, though. Certain data types are considered mutable, which means that the object can be changed without destroying it. When you update a variable holding an integer by saying x = x + 1, you’re really destroying x and inserting a new value.

If you pass a mutable object, any changes to it will be saved. Which objects are mutable? In general, if you can change an object through a method, it’s mutable. So, integers, strings, and floats aren’t mutable, but lists are.

Grouping Functions Within a Function

Once you have a number of functions, you’ll often want to have a function that ties them all together. This function is usually called main. You could name it something else, but if other developers look at your code, they’ll automatically understand a function called main is the core of your program.

Why bother tying functions together? Consider the following example. Someone has given you a very long script. It contains logic you’ve never encountered before, as well as some extremely unfamiliar code. You look at the main function:

def main():
    username = get_username()
    password = get_password()
    authenticated = authenticate(user=username, password=password)
    if authenticated:
         print_timesheet(username)
         add_hours(username)

What are some of the things you can guess about this program? You can see the programmer is getting a username and a password. A function called authenticate is called, which returns something. If whatever was returned is true, the program prints out a timesheet and then gets the user to add hours.

If you want to run your program as a script, you will need a way to call it. You could just call main from outside of a function, but there’s a better way: At the bottom of your file, outside of any functions, add two lines:

if __name__ == "__main__":
    main()

This looks like a bit of magic, but it’s really quite simple. __name__ is a special variable that Python sets when it runs a file. If it’s value is "__main__", that means we ran the file directly. If this is the case, Python calls main. Otherwise, nothing is done.

Sending a Varying Number of Parameters

Sometimes, you don’t know how many values the user might decide to send you. How can you build a function that’s flexible enough to deal with such a situation? In this case, you can use **kwargs.

When you add **kwargs to the end of your parameter list, Python will take any values that the user sent to the function (as long as they have a keyword) and store them in a data type called a dictionary.

Let’s create a function that takes two values and can accept any number of extra values. We’re going to send it a value with the parameter name item_three.

>>> def test_args(item_one, item_two, **kwargs):
...   print item_one
...   print item_two
...   print kwargs
...
>>> test_args(item_one="Hello", item_two="world", item_three="How are you?")
Hello
world
{'item_three': 'How are you?'}

When we print kwargs, we get something wrapped in curly braces. This is called a “dictionary.” In the dictionary, the keyword the user used is paired with the text he sent. You’ll learn next hour how to work with dictionaries.

The two asterisks are vital. They tell Python to take any extra parameters and save them.

If you don’t want to worry about keywords, you can use *args. *args takes any non-keyworded values and stores them in a tuple (a list you can’t edit). Here, we have a function that asks for two values and allows for args. We’re going to send it five values.

>>> def test_args(first, second, *args):
...   print first
...   print second
...   print args
...
>>> test_args(1, 2, 3, 4, 5)
1
2
(3, 4, 5)

The first two values were saved into the first two parameters. The final three, because we are out of parameters, were saved in args. Here, only one asterisk was necessary, because we were telling Python to save the extra values into a tuple.

Using Functions in the Real World

Let’s return to our waiter from previous hours who is tasked with keeping tallies for the tables and printing out receipts. Now that he knows about functions, he can make a program that not only prints out a more informative receipt, but also is much shorter and easier to maintain!

He has had some complaints from tables about each seat only being shown a total for that seat. They’d much rather see all the items for their seat and then a total. That way, the customers know exactly what was charged to their seat, and they can more easily check the bill for errors.

The waiter sits down and thinks about what he wants the receipt to look like. He comes up with something that looks like Figure 8.1.

Image

FIGURE 8.1 A sketch of what the waiter would like the receipt to look like.

He realizes he’s going to have to ask for each item for each seat, then total that, then add that to the grand total. Although this might make his original program absurdly long, with functions it’s hardly long at all.

He ends up adding two functions: one to print the items for a seat and another to get the total for a seat.

def print_seat(seat):
        for item in seat:
                print "${}".format(item)
        print "-"*15
        total = get_seat_total(seat)
        print "Total: ${}".format(total)

def get_seat_total(seat):
        total = 0
        for dish in seat:
                total = total + dish
        return total

def main():
        seats = [[19.95],
          [20.45 + 3.10],
          [7.00/2, 2.10, 21.45],
          [7.00/2, 2.10, 14.99]]

        grand_total = 0

        for seat in seats:
                print_seat(seat)
                grand_total = grand_total + get_seat_total(seat)
                print " "

        print "="*15
        print "Grand total: ${}".format(grand_total)

if __name__ == "__main__":
        main()

Now, when he runs the program, he gets this receipt:

$ python scripts/ch8_receipt.py
$19.95
---------------
Total: $19.95


$23.55
---------------
Total: $23.55


$3.5
$2.1
$21.45
---------------
Total: $27.05


$3.5
$2.1
$14.99
---------------
Total: $20.59


===============
Grand total: $91.14


Note: Lists in Lists

Note that the waiter used lists in lists. This is completely valid, because lists can contain any data type. This can cause things to get complicated quickly, however, so it’s usually not common practice once developers get better tools.


Summary

During this hour, you learned how to group blocks of code into something called functions. You learned how to send variables to functions through parameters and how to set defaults on those parameters. You also learned about scope and how functions create temporary variables. Finally, you learned how you can return values from functions.

Q&A

Q. Can I put a function in a function?

A. You can! Before you do this, just think about whether that bit of code could be used on its own. If so, you might want to make it its own function.

Q. Can I call **kwargs and *args something different?

A. Yes. While **kwargs and *args are standard, if you feel like calling them something different, you can do so. It does make your code a bit hard to read, though, for others that may want to work with you later, so only do this if it’s absolutely necessary.

Q. Can functions call themselves?

A. They can! This is called recursion, and is a bit more advanced. The function will call itself, but it will have a completely new scope. Once that function is done being executed (or a return is executed), the previous function call is finished. Think of it like a stack of plates, where you place one plate on top of another and then take them off the stack, from top to bottom.

Here’s a quick example of a function that uses recursion:

>>> def print_numbers(n):
...   print n
...   n -= 1
...   if n:
...      print_numbers(n)
...   n += 1
...   print n
...
>>> print_numbers(5)
>>> print_numbers(5)
5
4
3
2
1
1
2
3
4
5

With recursion, the most important thing to remember is to have some way for the function to finish. If we made it so we could never get past the if statement, our function would keep calling itself forever!

Workshop

The Workshop contains quiz questions and exercises to help you solidify your understanding of the material covered. Try to answer all questions before looking at the answers that follow.

Quiz

1. How are values passed into functions?

2. When is a variable in scope?

3. What do *args and **kwargs do?

Answers

1. Values are passed into functions through parameters.

2. A variable is in scope when you can reference it.

3. *args and **kwargs allow you to pass a varying number of parameters into a function.

Exercise

Write a program that gets a name from a user. If that name appears in a class list, then the program should tell the user that the student is in that class. If not, it should alert the user that there’s no student by that name. There should be a function that returns True if the student is present, and False if not. Your output should look like this:

Welcome to the student checker!
Please give me the name of a student (enter 'q' to quit): [student1]
No, that student is not in the class.
Please give me the name of a student (enter 'q' to quit): [student2]
Yes, that student is enrolled in the class!
Please give me the name of a student (enter 'q' to quit): q
Goodbye!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.4.181