4.6. Functions

This section contains examples motivating an important computing concept: functions.

Functions are named programs that take inputs and return results. The basic idea of a function is that it contains code that you want to reuse.

Consider one of our looping examples:

>>> rs = 'ABCDEFGHI'
>>> cs = '123456789'
>>> squares = []
>>> for row in rs:
       for col in cs:
           squares.append( row + col)

We are basically taking what is called the cross product of two sets: pair each member of the first set, with all the members of the second. The result is a list of strings that are two characters long:

['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9',
  'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9',
  'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9',
  'D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9',
  'E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9',
  'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
  'G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9',
  'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9',
  'I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9']

This is something we might want to reuse so let’s name it and define it as follows:

def cross (rows, cols):
    squares = []
    for row in rows:
      for col in cols:
          squares.append( row + col)
    return squares

Now we can use the function like this:

>>> rs     = 'ABCDEFGHI'
>>> cs     = '123456789'
>>> cross(rs, cs)
 ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9',
  'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9',
  'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9',
  'D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9',
  'E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9',
  'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
  'G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9',
  'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9',
  'I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9']

Several things are accomplished in the function definition:

  1. We are writing a piece of code to do a particular task.
  2. We are associating a name with it, cross. The rules for function names are the same as the rules for variable names; that is, case matters, no digits in first position, no dollar signs or asterisks, and so on.
  3. We select the parameters of the function, the temporary variables whose values need to be set in order for the function to complete its computation. These are rows and cols in the example. Notice how when we use the function, cross(rs,cs), it is followed by some expressions in parentheses. When the function is called (the technical term for “used”), the parameters rows and cols are set to be whatever it is applied to (the expressions in parentheses), in this case rs and cs; rs and cs are called the arguments of the function. A function is reusable because it can be applied to a variety of arguments in a variety of contexts.
  4. Something is returned. An object we may to use in further computation.

Keeping in mind these four tasks will help both with defining functions and with using them. Thus, the first step in going from some lines of code to a function is to observe that this is a task you will want to do over and over:

>>> rs = 'ABCDEFGHI'
>>> cs = '123456789'
>>> squares = []
>>> for row in rs:
       for col in cs:
           squares.append( row + col)

The next step is to choose a name. The third and final task is to look at the code lines and decide what names are being used within the code that need to be defined before the code-task can be completed. These will be the parameters of the function. In this case the double loop uses three names: rs, cs, and squares. But :squares is the name of the list containing the result of the computation. It does need to be defined before the loop is executed, but it is created only for the purpose of being used in the double loop. Thus, we define a function of two parameters, making sure that squares is defined in the first line of function code:

def cross (rows, cols):
    squares = []
    for row in rows:
      for col in cols:
          squares.append( row + col)
    return squares

Note the last line, which uses the Python keyword return. The return statement does not need to occur in the last line of a function definition, but it frequently does. Whenever Python encounters a return statement in the course of executing a function, it immediately stops executing the function and returns the value of the expression that follows the return statement.

It is perfectly legal to define a function with no return statement in it. In that case the function still returns something when the block of code completes its execution: the special Python object None. Functions that return None are useful for a variety of purposes, for example, user interactions, or some computation that changes the global computational context. But for now we will focus on functions that return a useful value. And that means we usually set a variable to what the function returns. Thus we would usually use cross as follows:

>>> my_squares = cross(rs, cs)
>>> my_squares
 ['A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9',
  'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9',
  'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9',
  'D1', 'D2', 'D3', 'D4', 'D5', 'D6', 'D7', 'D8', 'D9',
  'E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9',
  'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9',
  'G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'G9',
  'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'H8', 'H9',
  'I1', 'I2', 'I3', 'I4', 'I5', 'I6', 'I7', 'I8', 'I9']

Now the list of squares is available for future use.

Now we have actually defined cross without using the improvement we discussed when we introduced list comprehensions:

rows     = 'ABCDEFGHI'
cols     = '123456789'
col_units = [r+c for r in rows for c in cols]

Modifying cross to use a list comprehension is quite simple, and makes it easier to read and understand:

def cross (rows, cols):
  return [r+c for r in rows for c in cols]

This also illustrates how function definitions hide complexity. There are different ways the same function could be written. We can load a file that defines the function cross and call it to compute this list of squares without ever reading its definition or understanding exactly how it works. All we might care about is that it computes the cross-product of two sequences.

4.6.1. Reasons for using functions

Here are three major reasons for using functions:

  1. Reuse of code
  2. Hiding unnecessary complexity
  3. Making your code easier to understand

We’ve illustrated points 1 and 2. Now let’s illustrate point 3, using an example from the list comprehensions discussion.

Consider the code for creating all 9 box units in Sudoku puzzles, from that discussion:

box_units = [[l+n for l in lets for n in nums]
               for lets in ('ABC','DEF','GHI')
                 for nums in ('123','456','789')]

Notice that the expression:

[l+n for l in lets for n in nums]

pairs each letter in lets with each numeral of nums. Since there are 3 letters in each letter group, and three numerals in each numeral group, this gives us the 9 squares of a unit. This is a fairly complicated operation all on its own and might well benefit from being packaged up in a function definition.

Now notice that, exceopt for the the variable names, this is the same operation we just named cross:

def cross (rows,cols):
    return  [r+c for r in rows for c in cols]

So we can rewrite the computation on box_units as follows:

box_units = [cross(lets,nums)
               for lets in ('ABC','DEF','GHI')
                 for nums in ('123','456','789')]

Notice how much easier it is to understand. We know what the operation cross, so we understand what this code is doing: It takes the cross product of each of the letter groups with each of the number groups, 9 cross products in all, to make the set of box units. This is probably the major reason for using function definitions. It makes your program easier to to understand, especially if you give your functions names that make it clear what they do.

4.6.2. Further examples

Python functions can of course compute mathematical functions. Some examples follow, to help illustrate the idea of reusability.

The formula for the area of a circle is:

\text{A} = \pi r^{2}

A function for computing the area of a circle:

from math import pi
def circle_area(r):
  return pi * (r**2)

The formula for computing the volume of a sphere is:

V = \frac{4}{3} \pi r^{3}

A function for computing the volume of a sphere:

from math import pi

def sphere_volume(r):
  return (4.0/3) * pi * (r**3)

A slightly more efficient function for computing the volume of a sphere:

from math import pi
sphere_volume_constant = (4.0/3)*pi

def sphere_volume(r):
  return sphere_volume_constant * (r**3)

The formula for computing the volume of a cylinder is:

V = \pi r^{3} h

A function for computing the volume of a cylinder:

from math import pi
def cylinder_volume(r,h):
  return pi * (r**2) * h