Copyright © 2006 David Schmidt

Chapter 6A:
Building programs from ``small parts'': Functions


6A.1 Functions
6A.2 Writing our own helper functions
    6A.2.1 Documentation strings
6A.3 Testing one function at a time: Interactive testing
6A.4 Parameters
    6A.4.1 Interactive testing of functions with parameters
    6A.4.2 Functions can call other functions
    6A.4.3 Multiple parameters
    6A.4.4 Labelled (keyword) arguments
6A.5 Functions can return answers
6A.6 Foundations: pre- and post-conditions of functions
6A.7 Functions can use global variables
6A.8 Design: Data structures and maintenance functions
    6A.8.1 Data structures can be parameters
    6A.8.2 Foundations: Functions maintain data-structure invariants
6A.9 Design: Bottom-up, top-down, and modular-programming
    6A.9.1 Case study: Rebuilding the checkbook assistant program
6A.10 Design: A program creates a special world and functions give the language of that world
6A.11 Semantics of functions: local namespaces
6A.12 Summary


Look at the computer you use --- it was assembled from ``prefab'' pieces --- the display was built by one manufacturer, and it is bolted and wired to the ``motherboard'' and CPU, which were built by another. The case was built again by someone else, as was the disk drive and any other gadgets (printers, DVD drives, cameras) that are attached.

A little history

The idea of assembling a product from parts that fit together is not new. It was first devised by firearms manufacturers in the eighteenth century. Gunmakers found it impossible to build and service all the guns wanted by hunters (and, alas, soldiers). To cope, the gunmakers designed standard-sized parts that fitted together, like a jigsaw puzzle does, into a gun. This way, more guns could be built, and when a part on the gun broke, it could be replaced by a new, duplicate part.

The idea of standard parts was used by Henry Ford to develop the modern assembly line, where entire automobiles were built from standard-sized parts, connected in a standard way. Almost all products we buy today (even food!) are built using assembly-line techniques.

Programs are assembled from parts, too

Assembly-line ideas also apply to computer programs: all large computer programs are assembled from parts (``components'') that connect together in standard ways. Since programs look like recipes in an odd mathematical language, it is not immediately clear what it means to build a recipe from ``components.'' But if you think about how a book is built from a sequence of chapters, and how a person who first appears in one chapter reappears in a later chapter, then you are getting the idea --- the characters and settings are ``parts'' that are ``assembled'' into a plot. An author rarely writes a book from Chapter First to Chapter Last with all the characters, setting, and plot completely fixed in place, and large computer programs are rarely written by one person going from the first command to the last --- both books and programs are built from parts that connect together.

There are two standard ways of building ``parts'' of computer programs that connect together: functions and modules. Stated crudely, functions are ``small parts'' (like nuts and bolts) that are used in lots of places, and modules are ``big parts'' (like engine and transmission assemblies) that must be bolted together to make the final product. In this chapter, we learn how to manufacture the ``small parts''; the next chapter tells us about the ``big parts.''


6A.1 Functions

If we look at the programs in the previous two chapters, we see that they are messy --- they are getting too large to understand easily, mostly because they contain lots of little, distracting details. Also, the exact same details are appearing in more than one place --- some of the commands were written in one position and ``cut and pasted'' into another. This is bad, especially if we have to change the program some months in the future --- we will be forced to reread the entire program to find all the cut-and-paste coding that must be changed.

About fifty years of programming experience has proved that a human can understand and keep in mind about 50 lines of computer code at one time --- just 50 lines! This is not enough to build any real program. How can we develop programs that are longer? In addition to writing good comments for the code, the solution lies in writing the program in pieces (of no more than 50 lines each) and connecting them together, much like a book is written in chapters.

The function is the best-known construction for building program pieces. (Functions are also called ``subroutines,'' ``procedures,'' and ``methods.'' There are some nuances in using these terms, but they don't matter here.) Simply stated, a function is a sequence of commands bundled up with a name, and when we later mention the name, the bundled-up commands execute. (It is like when your instructor barks, ``Read Chapter 10!'' You ``execute'' Chapter 10.)

In your Python program, you are already using functions that were written by the Python designers. Let's review a few:

We have used these functions many times, and indeed, we often use them more than once in the same program. Such functions are the ``nuts and bolts'' of programming, because they are little ``packets of commands'' that do useful things when they execute. Without these functions, we would be forced to write long sequences of ugly commands for painting letters on the display, or talking to the buttons on the keyboard, or moving about the bits inside a computer word full of characters.

Just like there are different forms of parts for building a TV or car, there are different forms of functions:


6A.2 Writing our own helper functions

We can write functions like Python's print and len. Our functions behave like new Python ``magic words'' --- our functions literally expand the vocabulary of Python.

To write a function in Python, we state its name and we write its commands, indented, underneath. Here is a first example:

# error  prints a generic error message.
def error() :
    text = "ErrorErrorErrorErrorErrorErrorError\n"
    print text + text + text
The function is named error, and the keyword, def, tells us (and the Python interpreter) that this line defines a new function. The matching parentheses, () state that the function requires no argument. Note the colon afterwards. (The space between the right parenthesis and the colon is optional, and most people omit it.)

The function's commands (its body) are the two indented commands underneath.

If you wish, you may pretend that

A function, like error, is a ``little program'' that is started by typing its name. Most importantly, the little program can be started by another program.

Figure 1 shows a sample program that will manage cash withdrawals for an ATM (automated teller machine); the program asks the user to type a dollars-and-cents amount, and if the amount is in error, then the error function is executed by the main program:

FIGURE 1=================================================

# ATM1  lets a human withdraw cash from an automated teller machine.

# error  prints a generic error message.
def error():
    text = "ErrorErrorErrorErrorErrorErrorError\n"
    print text + text + text


# The ``main program'' starts here:

dollars = int(raw_input("Type dollars amount: "))
if dollars < 0 :
    error()  # this calls (executes) the function

cents = int(raw_input("Type cents amount: "))
if (cents < 0) or (cents > 99) :
    error() # this calls (executes) the function

# the step that does cash withdrawal goes here....

raw_input("\n\npress Enter to finish")

ENDFIGURE==========================================
The function is defined first, and the program follows. The program executes (calls or invokes) error() when the program receives bad input data.

It is always a good idea to include a line or two of comments that explain what the function does. This reminds us later how we should use the function.

Let's test the program:

$ python ATM1.py
Type dollars amount: -4
ErrorErrorErrorErrorErrorErrorError
ErrorErrorErrorErrorErrorErrorError
ErrorErrorErrorErrorErrorErrorError

Type cents amount: 123
ErrorErrorErrorErrorErrorErrorError
ErrorErrorErrorErrorErrorErrorError
ErrorErrorErrorErrorErrorErrorError
Here is an explanation of what happened:
  1. The Python interpreter reads the program and encounters def error():. Both the function's name and its body are saved in the program's namespace for later use. The function does not yet execute.
  2. At the next line, the user is asked to type an input. The user types -4, and the int, -4, is assigned to dollars.
  3. The condition, dollars < 0, computes to True, so error() is called:
  4. The user is asked to type a second input, and a similar sequence of steps occur.

Remember:

When a function is called, execution pauses at the call, and the commands in the function's body are retrieved and executed. When the function's body finishes, execution resumes at the point where it was paused.

Here are some diagrams that visualize these steps: The program starts in this configuration:


and error's definition is saved for later use. (Actually, the instruction number address of the start of error's body is saved.)

After the input is read and is found to be negative,

function error is called, and its commands are executed just as if they were copied into the position where the function was called:

The function's commands execute while the program is paused, and they use a new, private namespace that is constructed for the function's use:

Once the function finishes, execution resumes in the program:

The diagrams reveal that function error has its own, ``local'' namespace for its own, ''local'' variable, text. The function's namespace disappears when the function finishes.

It's true that function error does just a small task and is not essential, but compare the program in the Figure with the one that follows below, without the function --- The one below is a bit harder to understand because there are more details in the way:

========================

dollars = int(raw_input("Type dollars amount: "))
if dollars < 0 :
    text = "ErrorErrorErrorErrorErrorErrorError\n"
    print text + text + text

cents = int(raw_input("Type cents amount: "))
if (cents < 0) or (cents > 99) :
   text = "ErrorErrorErrorErrorErrorErrorError\n"
   print text + text + text

=============================
Also, if we decide to change the error message to something different, then we must change the message in two places in the second program. In the first program, we change just the function definition, say like this:
def error() :
    print "Error. Sorry.\n"
When we define ``helper'' functions like error for doing such ``little things,'' we will find it easier to customize or change our program in the future.

The examples of the error-printing functions show us the first use of functions:

A function can do one small thing that must be done over and over from different places in a program.
We call such functions helper functions, because they help us write smaller, simpler programs with function calls in place of duplicated, copy-and-paste codings.


6A.2.1 Documentation strings

It is important to insert into every function a comment that describes what the function does. This is so important in practice that the Python interpreter reads and saves the comment for later use. But you must write the comment as a string, placed immediately after the function's header line, if you want the Python interpreter to remember the comment. Here is the reformatted error function, using a Python-style documentation string:
def error() :
    """error  prints a generic error message."""
    text = "ErrorErrorErrorErrorErrorErrorError\n"
    print text + text + text
The documentation string is indented just like the rest of the body of the function, and it begins and ends with three double-quotes. (For this reason, the documentation string can be multiple-lines long.)

The documentation string causes no harm when the function executes, and in fact, you can now print it, using the print command. Try this:

print error.__doc__
This prints error's documentation string. Also, there are some development-environment tools for Python that use the documentation strings to generate on-line documentation pages for Python programs. So, we will use the documentation strings in our functions.

Most of Python's prewritten functions have documentation strings. For example, try

print raw_input.__doc__
and
import random
print random.randrange.__doc__


6A.3 Testing one function at a time: Interactive testing

A function is a ``small piece'' of a program, and like a small mechanical part, like a nut or bolt, we should be able to test the function by itself before we insert it into a program. Python makes this easy to do, by means of interactive testing.

Again, here is the error function:

def error() :
    """error  prints a generic error message."""
    text = "ErrorErrorErrorErrorErrorErrorError\n"
    print text + text + text
We can test the function by itself, interactively, like this:
  1. Place the function in a file by itself, say, Test.py.
  2. Open a command window and start the program interactively:
    $python -i Test.py
    >>>
    
    This tells the Python interpreter to read Test.py, even though it is an incomplete program. Notice the -i --- this states that, after the program is read, the Python interpreter allows you to use the functions and variables in Test.py just as if you had typed them by hand.
  3. Now you can interactively test the function in Test.py --- type
    >>> error()
    
    This executes the function, just as if it was called from within a program. You will see printed, as expected,
    ErrorErrorErrorErrorErrorErrorError
    ErrorErrorErrorErrorErrorErrorError
    ErrorErrorErrorErrorErrorErrorError
    
    And, you can read its documentation string:
    >>> print error.__doc__
    'error  prints a generic error message.'
    
  4. Perhaps as a result of our testing, we rewrite error, say, to this:
    def error() :
        """error  prints a generic error message."""
        print "Error. Sorry.\n"
    
    We save this change in a text editor, we stop the Python interpreter (type exit), and we restart it:
    $python -i Test.py
    >>>
    
    Now we can test the revised function:
    >>> error()
    Error. Sorry.
    
In this way, we can repeatedly load, test, and modify a function until is behaves like we want.


6A.4 Parameters

Sometimes we write programs that require arguments from the human user. The programs are written like this:
message = raw_input("Please type something: ")
... compute with the  message ...
Functions might also require arguments, but they do not receive them via raw_input. Instead, the arguments are given to the function by the program that calls it. The argument enters the function via a new variable, called a parameter. Here is one example:
===============

def errorMessage(message) :
    """errorMessage  prints a specific error message.

       parameter: message - a string, stating the error
    """
    text = "Error: " + message
    print  text + " Sorry.\n"

===================
The definition line begins with def, followed by the function's name, followed by a variable name in parentheses, and then the colon. (The documentation string will be examined in a moment.) Here, message is a Python variable, just like variable text. It is called a parameter (or formal parameter), and it ``connects'' to an argument, somewhat like this:
message = ... the argument that arrives when the function is called ...
text = "Error: " + message
print text + "\nSorry."
The main program might call the function like this:
errorMessage("You typed a negative number.")
This starts function errorMessage so that
message = "You typed a negative number."
executes first, and then the commands
text = "Error: " + message
print text + " Sorry.\n"
execute next.

The documentation string summarizes what the function does and explains how the parameter will be used within the function. This information is helpful when others call the function. (Also, it is a standard Python style to leave a blank line between the first line of the documentation string, which explains what the function does, and the rest, which explains the parameter(s).)

In errorMessage("You typed a negative number."), the "You typed a negative number." part is called the argument. When the function is called, the argument is assigned to the parameter.

Let's test the function in an improved ATM program:

FIGURE 2=================================================

# ATM2  lets a human withdraw cash from an automated teller machine.

def errorMessage(message) :
    """errorMessage  prints a specific error message.

       parameter: message - a string, stating the error
    """
    text = "Error: " + message
    print  text + " Sorry.\n"

# The program starts here:

dollars = int(raw_input("Type dollars amount: "))
if dollars < 0 :
    errorMessage("You typed a negative number.")

cents = int(raw_input("Type cents amount: "))
if (cents < 0) or (cents > 99) :
    errorMessage("The cents amount was not in the range 0..99.") 

# the step that does cash withdrawal goes here ...

raw_input("\n\npress Enter to finish")

ENDFIGURE===========================================
Here is the test:
$ python ATM2.py
Type dollars amount: -5
Error: You typed a negative number. Sorry.

Type cents amount: 123
Error: The cents amount was not in the range 0..99. Sorry.
The program shows that the function can be called with distinct arguments, and each call prints a different message, because of the distinct arguments.

Here is the execution of the test program, drawn in pictures, showing how arguments and parameters are used: The program starts and the function is saved:


The input is read, saved, and the first condition is True:

This calls errorMessage. The execution proceeds just like the function's body is copied into the position of the function call with the assignment, message = "negative":

The parameter is saved in the function's namespace, and the function's commands execute:

When the function finishes, the program resumes:

Look again at the function's comment:

def errorMessage(message) :
    """errorMessage  prints a specific error message.

       parameter: message - a string, stating the error
    """
    text = "Error: " + message
    print text + " Sorry.\n"
The third line of the documentation string explains that the argument to be assigned to the parameter must be a string. Notice, for example, that this call causes a stoppage of the program:
errorMessage(99)
A function's argument is its ``assumed input,'' and the function cannot guarantee its work if the argument is inappropriate for the parameter.

When you define a function, always document its purpose and its parameters with a documentation string.

A function can be called with an argument that is a compound expression, like this:

data = raw_input("Type cents amount: ")
cents = int(data)
if (cents < 0) or (cents > 99) :
    errorMessage("The input, " + data + ", is incorrect.")
Here, the expression, "The input, " + data + ", is incorrect." is computed to its answer, a string, before function errorMessage starts its work. For example, if the user types 123 as the input for variable data, then the argument to the function computes to "The input, 123, is incorrect." and only then does the function start (by assigning the string to parameter message).


6A.4.1 Interactive testing of functions with parameters

Functions that use parameters can be difficult to write correctly, so testing is crucial. We can place the function,
def errorMessage(message) :
    """errorMessage  prints a specific error message.

       parameter: message - a string, stating the error
    """
    text = "Error: " + message
    print text + " Sorry.\n"
in a file, Test.py, and test it:
  1. We open a command window, and start the Python interpreter interactively:
    $python -i Test.py
    >>>
    
  2. We repeatedly test the function with different arguments to observe its behavior:
    >>> errorMessage("too bad.")
    Error: too bad. Sorry.
    
    >>> errorMessage("R2D2 " + "is lost!")
    Error: R2D2 is lost! Sorry.
    
    >>> errorMessage(13)                  
    Traceback (most recent call last):
      File "", line 1, in ?
      File "Test.py", line 12, in errorMessage
        text = "Error: " + message
    TypeError: cannot concatenate 'str' and 'int' objects
    
    >>> print errorMessage.__doc__
    errorMessage  prints a specific error message.
    
    parameter: message - a string, stating the error
    
    Repeated tests are necessary to verify that the arguments are used correctly by the function.


6A.4.2 Functions can call other functions

Parts are sometimes built from other parts, and similarly, functions can call other functions. Here is a simple example --- remember function error?
======================

def error() :
    """error  prints a generic error message."""
    text = "ErrorErrorErrorErrorErrorErrorError\n"
    print text + text + text

======================
We might write another function that calls it:
====================

def printBigError(message) :
    """printBigError prints an error message surrounded by an annoying border

       parameter: message - a string, containing the error message
    """
    error()
    print message
    error()

============================
When we call the new function, say, with
printBigError("I am so totally lost!")
We get this printout:
ErrorErrorErrorErrorErrorErrorError
ErrorErrorErrorErrorErrorErrorError
ErrorErrorErrorErrorErrorErrorError

I am so totally lost!
ErrorErrorErrorErrorErrorErrorError
ErrorErrorErrorErrorErrorErrorError
ErrorErrorErrorErrorErrorErrorError
Here are diagrams that show what happened. We begin with this configuration:

the function assigns the argument to parameter message, as usual. Then, execution within printBigError is paused, because a second function, error() is called:

Once the commands within error finish, then execution can resume with print message:

The second call to error executes next:

and finally all the called functions finish:


6A.4.3 Multiple parameters

It is OK for a function to have more than one parameter. Here is an example:

def printAlert(priority_level, time, message) :
    """printAlert  prints an alert message

       parameters: priority_level - an int, stating the level of the alert
                   time - a string, stating the time the alert was issued
                   message - a string, stating the warning
    """
    print "Alert!  Priority", priority_level
    print time + " - " + message
There are three parameters: an integer followed by two strings. When the function is called, there must be three arguments that match the parameters:
printAlert(1, "12:01p.m.", "it's lunchtime!")
The three arguments are assigned, one by one, to the parameters, like this:
priority_level = 1
time = "12:01p.m."
message = "it's lunchtime!"
and the rest of the commands execute, printing,
Alert!  Priority 1
12:01p.m. - it's lunchtime!


6A.4.4 Labelled (keyword) arguments

Look again at the printAlert function; it isn't so easy to remember the order we must use for its three arguments. In this situation, Python allows you to call the function with keyword arguments, where you write the assignment statements between the arguments and their parameters. You do it like this:
printAlert(time = "12:01p.m.", message = "it's lunchtime!", priority_level = 1)
The order of the arguments no longer matters.


6A.5 Functions can return answers

Often we write programs that print their answers on the display; the programs might look like this:
message = raw_input("Type something: ")
 ... compute an  answer  from the  message ...
print answer
Functions sometimes compute answers, too, but they do not always print them. Instead, they return them to the place in the program that called the function.

We have used this form of function before: Python's built-in functions, raw_input and int, return answers. We make our functions return an answer by using the Python command, return.

Here is a small function that computes and returns the reciprocal of its argument:

def reciprocal(n) :
    """reciprocal  computes the reciprocal of its argument

       parameter: n - an int, which must not equal 0
       returns: the float,  1.0/n   (When  n==0,  0.0 is returned.)
    """
    answer = 0.0 
    if n != 0 :
        answer = (1.0 / n)
    return answer
The last line, return answer, commands that the number named by answer is inserted at exactly the position where the function was called.

For example, if the calling command was this:

one_third = reciprocal(3)
then the answer, 0.33333333333333331 ``replaces'' the function's call and is assigned to variable one_third. Or, if the calling command was this:
print "1/3 is", reciprocal(3)
then the answer ``replaces'' the function's call and we see printed,
1/3 is 0.33333333333333331

Here is a second example, where we write a function to do something slightly complicated and important. (We will use the function in the ATM program were are building.)

Accounting programs maintain balances in cents-only format (for example, $30.95 is stored as 3095), because bankers hate to lose fractions of pennies due to fractional-arithmetic imprecision. But when we print a cents-only amount, we must reformat it as a dollars-cents string (so that 3095 prints as "30.95"). Here is a function that does the reformatting:

============================

def formatDollarsCents(amount) :
    """formatDollarsCents formats a cents amount into a dollars.cents string

       parameter: amount - a nonegative int, a cents-only amount
       returns: a string, formatted as  dollars.cents. 
         (If  amount  is < 0, then the string, 'negative' is returned.)
    """
    if amount >= 0 :
        dollars = amount / 100
        cents = amount % 100
        import string  # load  string  module with helper functions
        answer = "$" + str(dollars) + "." + string.zfill(cents, 2)
    else :
        answer = "negative"
    return answer

=================================
answer receives a string that correctly formats the argument in dollars-cents format. The string is returned as the answer that is inserted at exactly the position where the function was called.

Say that we test the function with this:

current_balance = 3205   # 32 dollars and 5 cents
print "Balance is " + formatDollarsCents(current_balance)
we see
Balance is $32.05
printed. This is because
  1. current_balance is assigned 3205.
  2. To learn the string it must print, the print command computes the expression,
    "Balance is " + formatDollarsCents(current_balance)
    
    To compute this, formatDollarsCents(current_balance) is invoked.

    Execution pauses in the middle of the expression so that formatDollarsCents can compute and return an answer:

  3. Execution resumes, and "Balance is " + "$32.05" computes to "Balance is $32.05".
  4. The string becomes the argument to the print function, which prints it on the display.

The formatting function will will be used within the ATM program. The function contained some delicate computation steps, and it was good that we wrote and tested the function seperately. Indeed, this is a second standard use of functions:

A function can be a ``little program'' that does an important activity.
Such a function should be designed and tested by itself before it is inserted into a completed program.

The improved ATM program

Here is the latest version of the ATM program; it uses the function we just wrote to print the amount the user wishes to withdraw:

FIGURE===================================================

def errorMessage(message) :
    """errorMessage  prints a specific error message.
    
       parameter: message - a string, stating the error
    """
    text = "Error: " + message
    print  text + " Sorry.\n"

def formatDollarsCents(amount) :
    """formatDollarsCents formats a cents amount into a dollars.cents string

       parameter: amount - a nonegative int, a cents-only amount
       returns: a string, formatted as  dollars.cents.
         (If  amount  is < 0, then the string, 'negative' is returned.)
    """
    if amount >= 0 :
        dollars = amount / 100
        cents = amount % 100
        import string  # load  string  module with helper functions
        answer = "$" + str(dollars) + "." + string.zfill(cents, 2)
    else :
        answer = "negative"
    return answer

# The program starts here:

OK = True
dollars = int(raw_input("Type dollars amount: "))

if dollars < 0 :
    errorMessage("You typed a negative number.")
    OK = False

if OK :
    cents = int(raw_input("Type cents amount: "))
    if (cents < 0) or (cents > 99) :
        errorMessage("The cents amount was not in the range 0..99.") 
        OK = False

if OK :
    request = (dollars * 100) + cents
    print "Amount to withdraw is " + formatDollarsCents(request)
    # ... subtract request from the account's balance
    #     and emit cash ...

raw_input("\n\npress Enter to finish")

ENDFIGURE=================================================


6A.6 Foundations: pre- and post-conditions of functions

A function is a ``little program,'' and it will compute an appropriate answer if it is given appropriate arguments for its parameters. The documentation strings that we write describe the appropriate arguments and answers.

The restrictions on a functions argument that make the argument appropriate are called preconditions, and the form of answer that is computed and returned is called a postcondition. Often the precondition and postcondition are stated in informal English, but if we wish to formalize the algebraic knowledge produced by a function, we can write pre- and postconditions in an algebraic way.

For example, here is the reciprocal function from the previous section, where its documentation string is augmented with more precisely stated pre- and postcondition information:

================================================
def reciprocal(n) :
    """reciprocal  computes the reciprocal of its argument

       parameter: n - an int, which must not equal 0
         precondition:  n != 0

       returns: the float,  1.0/n   (When  n==0,  0.0 is returned.)
         postcondition:  answer = 1.0/n
    """
    answer = 0.0 
    if n != 0 :
        answer = (1.0 / n)
    return answer

======================================================
The precodition lists an algebraic condition under which the function will behave properly, and the postcondition lists the algebraic property of the function's answer (named, ``answer'') that holds true, provided that the function's arguments made the precondition hold true.

Let's look more closely at this relationship, that is, how a precondition can be used to calculate how a function behaves and calculates the knowledge asserted in the postcondition. We use the usual algebraic reasoning on the function's body:

# assert the precondition:  n != 0
answer = 0.0
# assert:  n != 0  and  answer = 0.0
# implies:  n != 0,  meaning that the test the follows must equal True
if n != 0 :
    answer = (1.0 / n)
    # assert:  n != 0  and  answer = 1.0 / n
# assert:  n != 0  and  answer = 1.0 / n
return answer
# assert the postcondition:  answer = 1.0 / n
With the precondition, we guarantee that the if-command will compute its test to True and the division will take place. This ensures the truth of the postcondition.

A function's pre- and postconditions are used to do logical reasoning when the function is called. For example, the main program might invoke reciprocal. We can reason about the knowledge that is produced by the function call, like this:

# In the main program:
x = ...
# assert:  x > 2
# implies: x != 0   (this makes true  reciprocal's  precondition)
y = reciprocal(x)
# assert: y = 1.0/x   (because  reciprocal's  postcondition must be true)
Since we proved that the precondition is make true by the argument, x, we can assert the postcondition for variable y, which receives the result of the invocation.

Here is a more precise statement of the reasoning principle for function calls. For a function,

def f(params)
    """precondition:  PRE(params)
       postcodition:  POST(answer)
    """
    COMMANDs
and an invocation of it, x = f(args),
  1. immediately before the invocation, we calculate that the logical condition, PRE(args), holds true, that is, where args are substituted for params;
  2. then we assert that POST(x) holds true after the function finishes, that is, where x is substituted for answer.
We saw this reasoning applied in the previous call to reciprocal:
x = ...
# assert:  x > 2
# implies: x != 0   

# Since  reciprocal's precondition is  n != 0, 
#   and since  x  is the argument for parameter  n,  we  must show that
#     x != 0   to show that we are calling  reciprocal correctly.
#   This is true here.

y = reciprocal(x)

# reciprocal's postcondition is  answer == 1.0/n, 
#   and since the answer is assigned to  y,  we have that
#     y == 1.0/x  
Since we proved that the precondition is true for argument, x, we asserted the postcondition for variable y.

Of course, the computer will let us call reciprocal incorrectly, like this:

x = 0
y = reciprocal(x)
but no useful knowledge is produced from the call, because the function's precondition is False. If it is crucial that the function is computed only when its precondition is True, then we might use a Python assert command to enforce the precondition. This might be done as follows:
================================================ 
def reciprocal(n) :
    """reciprocal  computes the reciprocal of its argument

       parameter: n - an int, which must not equal 0
         precondition:  n != 0

       returns: the float,  1.0/n   (When  n==0,  0.0 is returned.)
         postcondition:  answer == 1.0/n
    """
    assert n != 0   # check the precondition before proceeding
    answer = (1.0 / n)
    return answer

======================================================

Here is a second example where pre- and postconditions help us understand precisely what a function accomplishes: The formatting function for monetary amounts might have these pre- and postconditions:

=======================================================

def formatDollarsCents(amount) :
    """formatDollarsCents formats a cents amount into a dollars.cents string

       parameter: amount - a nonegative int, a cents-only amount
       precondition:  amount >= 0

       returns: a string, formatted as  dollars.cents.
         (If  amount  is < 0, then the string, 'negative' is returned.)
       postcondition: answer == "$D.C"  and  (D * 100) + C == amount
    """
    if amount >= 0 :
        dollars = amount / 100
        cents = amount % 100
        import string  # load  string  module with helper functions
        answer = "$" + str(dollars) + "." + string.zfill(cents, 2)
    else :
        answer = "negative"
    return answer

=========================================================
The postcondition relates the structure of the answer string to the function's parameter. This knowledge ensures users of the function that the function computes correct information for printing, like this:
money =  ...
# assert: money >= 0
print formatDollarsCents(money)
# assert: "$D.C" printed, where  (D*100) + C == money

Using preconditions and postconditions, we can write precise algebraic properties of functions and insert these properties into the programs that call the functions. In this way, we can continue to calculate the precise knowledge calculated by the main program that calls the functions.


6A.7 Functions can use global variables

Functions that do ``little things'' are usually self contained: Arguments are deposited into their parameters, and the functions print or return answers. But functions can also build and help maintain a program's data structure, which is not contained within the function. Such a data structure is sometimes called a global variable.

We can understand global variables with a simple example: Say that a program uses a ``clock,'' and every sixty clock ticks, an alert must sound. The program makes a variable for the clock and starts it at zero:

clock = 0 
Within the program, there are places where the clock should tick --- say, after every third instruction. It is best to write a helper function, tick(), to make the clock tick by 1. The function is called within the program somewhat like this:
  ... # command 1
  ... # command 2
  ... # command 3
  tick()
  ... # command 4
  ... # command 5
  ... # command 6
  tick()
  ... # and so on
Let's write the function. If we try to write it like this:
def tick() :  # this version does not work correctly; read below....
    clock = clock + 1
    if  (clock % 60) == 0 :  # time to sound an alert ?
        print "\a", "Alert: time is", clock
then, the first time we execute tick(), we see this Python error message:
>>> tick()
Traceback (most recent call last):
  File "", line 1, in ?
  File "", line 2, in tick
  UnboundLocalError: local variable 'clock' referenced before assignment
The message tries to tell us tick is confused about variable clock, because the variable is not inside (``local to'') the function.

Here is the repair: When a function uses a variable that is not internal to its body, the function must assert that the variable is found elsewhere --- the variable is global.

Here is the correct coding and usage of function tick, which increments the ``global variable,'' clock:

FIGURE============================

# TestClock  tests a function that uses a global variable.

# Here is the ``global variable'':
clock = 0

def tick() :
   """tick  makes the clock increase in time by 1 tick. 
      Every 60 ticks, an alert is sounded and printed.
   """
    global clock  # asserts that variable  clock  is found outside the function
    clock = clock + 1
    if (clock % 60) == 0 :  # time to sound an alert ?
        print "\a", "Alert: time is", clock   # "\a" rings the keyboard bell


# The program starts here:  After every print command, the clock ``ticks'',
#  and then the program pauses (``sleeps'') for 0.1 seconds:

# Important:  To stop this program, press both  Ctrl and  c  

import time
while True :
   print " "
   tick()  # it's time to move forward the clock!
   time.sleep(0.1)  # pause 0.1 seconds

ENDFIGURE==============================================
Look at the first command in the body of tick: global clock tells the Python interpreter where to find the variable that must be incremented. When the function is called and the global clock command is executed,

a notation is placed in tick's namespace that variable clock is found in the ``global'' namespace.

When the following assignment, clock = clock + 1 executes, the notation in tick's namespace directs the computer to find the value of clock in the global namespace. The assignment to clock is done to the variable in the global namespace, and the function finishes:

The ATM program with a global variable

The ATM program we are developing should consult the customer's bank account to learn the customer's balance because conducting any withdrawal.

In reality, the customer' bank account is kept in a separate database program at the bank. Such a database will be a data structure --- like a list --- of customers' balances. As a simple example, say that each customer has a bank-account number that is a nonnegative int, such as 0 or 1 or 2 .... Then, we can keep the balances for the bank's customers in a list. For example, if the bank has three customers, where acouunt 0 has a balance of $100.50, and account 1 has a balance of $30.00, and account 2 has a balance of $250.66, then the list of accounts might be defined as

all_accounts = [10050, 3000, 25066]
where the amounts are stored in pennies.

If a customer wishes to withdraw money from their account, the customer's account in the list must be consulted, and the amount withdrawn must be subtracted from the account's balance. It is best to write functions that get a customer's balance and that do withdrawals. This is because the list of accounts is a critical piece of information (to the bank!) and it is best to use maintenance functions that correctly maintains the list.

========

# BankDatabase is a simple simulation of a bank's database.

# The database remembers the cash saved for each account number.
# This is done with a list of ints.  For example, the list,
#    all_accounts = [10050, 3000, 25066]
# has recorded that 
#   acouunt 0 has a balance of $100.50, and
#   account 1 has a balance of $30.00,  and
#   account 2 has a balance of $250.66

# Here is a sample database to get started.
#  (Usually, this information is read from a disk file.)
all_accounts = [10050, 3000, 25066]

#### Maintenance functions for the database:

def getBalance(account_num) :
    """getBalance returns the account's balance

       parameter: account_num - an int, the account number
       returns the  balance, in cents
    """
    global all_accounts
    return all_accounts[account_num]

def withdraw(account_num, cash) :
    """withdraw  removes  cash  from  balance.  If cash is greater than balance,
       then only the remaining balance is withdrawn.

       parameters: account - an int, the account number
                   cash, an int, a cents-only amount
       returns the amount of cash withdrawn from the balance
    """
    global all_accounts
    balance = all_accounts[account_num]
    if cash > balance :
        amount_withdrawn = balance
    else :
        amount_withdrawn = cash
    # revise the account info:
    all_accounts[account_num] = balance - amount_withdrawn
    return amount_withdrawn

========
These two functions ``maintain'' the bank-account. Since the two functions share the list, all_accounts, it is global to both. For example, if an ATM wishes to learn the balance for account 2, it would call the function, getBalance, like this:
balance = getBalance(2)
and a withdrawal of $10 from the same account would be done by this function call:
cash_withdrawn = withdraw(2, 1000)

Here is the final version of the ATM program with all its functions in place. It uses the simulated bank database seen above.

FIGURE===================================================

# ATM  lets a human withdraw cash from an automated teller machine.

def errorMessage(message) :
    """errorMessage  prints a specific error message.

       parameter: message - a string, stating the error
    """
    text = "Error: " + message
    print  text + " Sorry.\n"

def formatDollarsCents(amount) :
    """formatDollarsCents formats a cents amount into a dollars.cents string

       parameter: amount - a nonegative int, a cents-only amount
       precondition:  amount >= 0

       returns: a string, formatted as  dollars.cents.
         (If  amount  is < 0, then the string, 'negative' is returned.)
       postcondition: answer == "$D.C"  and  (D * 100) + C == amount
    """
    if amount >= 0 :
        dollars = amount / 100
        cents = amount % 100
        import string  # load  string  module with helper functions
        answer = "$" + str(dollars) + "." + string.zfill(cents, 2)
    else :
        answer = "negative"
    return answer


##### The program starts here:

print "Please insert your ATM card so that"
account = int(raw_input("I can read your account number: "))
#  NOTE:  a real ATM would use a different operation than  raw_input

OK = True
dollars = int(raw_input("Type dollars amount to withdraw: "))

if dollars < 0 :
    errorMessage("You typed a negative number.")
    OK = False

if OK :
    cents = int(raw_input("Type cents amount to withdraw: "))
    if (cents < 0) or (cents > 99) :
        errorMessage("The cents amount was not in the range 0..99.")
        OK = False

if OK :
    request = (dollars * 100) + cents
    print "\nRequest to withdraw " + formatDollarsCents(request)
    money = withdraw(account, request)
    # NOTE: if the program was wired to a real ATM, the ATM would now
    #  dispense the requested  money 
    print formatDollarsCents(money), "withdrawn"
    new_balance = getBalance(account)
    print "Balance is", formatDollarsCents(new_balance)

raw_input("\nTransaction completed")

ENDFIGURE================================================

Of course, a real-life bank database would be a huge data structure of bank accounts that live in a separate program on a separate computer. The ATM program would communicate with the database to obtain balance and withdrawal information. In such a case, the functions are used by the ATM program to communicate with the database. Even though our ATM example is simplistic, it is faithful to a real-life ATM and bank database. In the next chapter, we learn how to write programs in separate files that can communicate with each other by means of functions.


6A.8 Design: Data structures and maintenance functions

The clock and ATM examples in the previous section showed how functions can correctly maintain a global variable. We call such functions maintenance functions. This mating of global-variable-plus-maintenance-functions is extremely important to building large programs, and it is so important that we give three examples now.

Bank account

In the previous section, we modelled a bank account that might be used with an ATM. The example is important and we repeat it here, along with some additional functions that let a bank officer add new accounts and make deposits.

==================================================

# BankDatabase is a simple simulation of a bank's database.

# The database remembers the cash saved for each account number.
# This is done with a list of ints.  For example, the list,
#    all_accounts = [10050, 3000, 25066]
# has recorded that 
#   acouunt 0 has a balance of $100.50, and
#   account 1 has a balance of $30.00,  and
#   account 2 has a balance of $250.66

#  Here are some sample accounts to get started.
#  Usually, this information is read from a disk file.
all_accounts = [10050, 3000, 25066]

# Maintenance functions for the database:

# A banker uses these functions to create new accounts and deposit money:

def addAccount(cash) :
    """addAccount  creates a new bank account and assigns to it
       an account number

       parameter: cash - an int, the initial deposit
       returns: the account number, an int, for the newly created account
    """
    global all_accounts
    all_accounts = all_accounts + [cash]
    new_account_num = len(all_accounts) - 1
    return new_account_num

def deposit(account_num, cash) :
    """deposit adds cash to an account.

       parameters: account_num - an int, the account number
                   cash - an int, the amount to deposit
    """
    global all_accounts
    all_accounts[account_num] = all_accounts[account_num] + cash


#  The ATMs send input to these functions, which compute and return answers:

def getBalance(account_num) :
    """getBalance returns the account's balance

       parameter: account_num - an int, the account number
       returns the  balance, in cents
    """
    global all_accounts
    return all_accounts[account_num]


def withdraw(account_num, cash) :
    """withdraw  removes  cash  from  balance.  If cash is greater than balance,
       then only the remaining balance is withdrawn.

       parameters: account - an int, the account number
                   cash, an int, a cents-only amount
       returns the amount of cash withdrawn from the balance
    """
    global all_accounts
    balance = all_accounts[account_num]
    if cash > balance :
        amount_withdrawn = balance
    else :
        amount_withdrawn = cash
    # revise account info:
    all_accounts[account_num] = balance - amount_withdrawn
    return amount_withdrawn

=========================================================

The functions, withdraw and getBalance, do the maintenance and are carefully written so that the value of balance is never altered incorrectly (e.g., a negative value for balance never arises). People and ATMs that wish to examine and alter the account's balance use the maintenance functions to do this delicate work --- they do not assign to balance directly.

The above coding would almost certainly live in a separate file on a separate computer; the bank's employees and the ATM's would be situated on other computers and use their own programs to call the withdraw and getBalance functions. (We learn how to make this arrangement in the next Chapter.)

Office telephone directory

Say that our company must build and maintain an electronic telephone directory that is shared by its employees. For design reasons and for practical reasons, we build the telephone directory by itself: We choose a data structure for the telephone directory and functions that will correctly maintain the directory.

Perhaps the directory looks like the one from the previous chapter,


so we use a dictionary data structure, where information is stored like this:
tel_book = {"Jane Sprat": 4098, "Jake Jones": 4139, "Lana Lang": 2135}

The company's employees wish to (i) look up phone numbers, (ii) insert new people and their phone numbers, and (iii) delete people who quit the company. Since there are three actions one can do with the telephone book (dictionary), we write three maintenance functions, one for each action.

Each function is simple to write, and we quickly design and code the computerized telephone book:

========================================================

# The telephone book:  It maps a person's name to their phone number,
#  e.g.,  tel_book = {"Jane Sprat": 4098, "Jake Jones": 4139, "Lana Lang": 2135}
tel_book = { }

# The functions that maintain the telephone book:
def lookup(name) :
    """lookup finds the phone number for  name  and returns it

       parameter: name - the person we look for
         precondition: name in tel_book
       returns: the phone number for name.  If  name not in the book, -1  returned
         postcondition:  number == tel_book[name]
    """
    global tel_book
    number = -1
    if name in tel_book :
        number = tel_book[name]
    return number

def insert(name, number) :
    """insert  inserts the new  name and number into the book.
       If the name is already in the phone book, _no action is taken_!
   
       parameters: name - the person;  number - their phone number
         precondition:  name not in tel_book
         postcondition:  tel_book[name] == number
    """
    global tel_book
    if not(name in tel_book) :
        tel_book[name] = number

def delete(name) :
    """delete removes the entry for  name  in the book.

          precondition:  name in tel_book
          postcondition:  name not in tel_book
    """
    global tel_book
    if name in tel_book :
        del tel_book[name]

=================================================================

Next, we should place this coding in a file and interactively test the functions, one at a time. The test session might go like this:

$python -i Phone.py
>>> print tel_book
{}
>>> insert("ed", 3456)
>>> print tel_book
{'ed': 3456}
>>> insert("maria", 1111)
>>> print tel_book
{'ed': 3456, 'maria': 1111}
>>> lookup("maria")
1111
and so on. Notice that, in between our function testing, we can interactively peek inside the global variable and see its value. This is useful for checking that the variable is correctly updated by the functions. We are allowed to do this because we are the software developers, and we want to see all the details within the program we build. But the people who will use our telephone book will not do this --- they will use the functions as the ``entry points'' into the telephone book.

Slide puzzle

When we model within the computer a chess game or the world's weather patterns, we build an electronic version of the real-world object we study. The standard approach to computer modelling a physical object is to define a data structure to represent the object and define functions that manipulate and maintain the object.

Last chapter we saw how to use a grid to model a slide puzzle, which is a simple example of a physical game board. We can extract the modelling of the puzzle and its function that slides puzzle's pieces. Here is the data structure and its maintenance functions:

==========================================================

# Models a slide puzzle holding positive numbers and one empty space.

PUZZLE_SIZE = 4  # the puzzle's size
puzzle = [ [15, 14, 13, 12],    # the puzzle.  0 marks the empty space
           [11, 10,  9,  8],
	   [ 7,  6,  5,  4],
	   [ 3,  2,  1,  0] ]
         # data invariant: puzzle's numbers form a permutation of  0..15
empty_space = (3, 3)  # remembers the location of the empty space (0)


def move(num) :
    """move  attempts to move the piece labelled by  num  into the empty space.

       parameter: num - the number to move; must be adjacent to the empty space.
         precondition:  num > 0  and  num < PUZZLE_SIZE * PUZZLE_SIZE 
       returns: True, if num slides into the empty space; returns False otherwise.
    """
    global PUZZLE_SIZE, puzzle, empty_space  # we use all three variables....
    success = False
    piece = ()  # will remember the cell coordinates where  num  rests
    for i in range(PUZZLE_SIZE):   # search for  num  in  puzzle:
        for j in range(PUZZLE_SIZE) :
	    if num == puzzle[i][j] :
	        piece = (i, j)  # we found  num  at coordinates  i,j

    if piece != () :   # we found num; let's try to move it:
	i = piece[0] 
	j = piece[1]
        if empty_space == (i-1, j)    \   # adjacent to the empty space?
           or empty_space == (i+1, j) \
           or empty_space == (i, j-1) \
           or empty_space == (i, j+1) :
	    # if True, it's ok to move  num  into the empty space:
            puzzle[empty_space[0]][empty_space[1]] = num
	    puzzle[i][j] = 0  # the new empty space
	    empty_space = (i, j)
            success = True
    return success


def printPuzzle() :
    """printPuzzle  displays the  puzzle's  contents"""
    global puzzle
    import string 
    for row in puzzle :
        for item in row :
            if item == 0 :
                print "  ",
            else :
                print string.zfill(item, 2),  # print a two-symbol number
        print


=============================================================
The difficult details about sliding a piece into the puzzle's empty space are all contained within function move. Simularly, the details of formatting of the puzzle for printing are contained within printPuzzle. This makes it truly easy to write programs that play with the puzzle:
===========================

while True :
    printPuzzle()
    num = int(raw_input("Type number of piece to move: "))
    OK = move(num)
    if not OK :
        print "Illegal move --- try again"

================================

Summary

The three examples convey this third, important use of functions:
A function can do maintenance to a global variable (data structure) to keep the variable correctly updated.
When the global variable and its maintenance functions are used by a larger program, the the program calls the maintenance functions to do the correct updating of the global variable.

In the next chapter, we will learn how to package a global variable and its helper functions into a separate file --- a module --- which is a ``big part'' or ``subassembly'' that can be connected together to other files to assemble a big program.


6A.8.1 Data structures can be parameters

When a function is called, arguments are assigned to parameters. This is just assignment. Since we can assign data structures like sequences and dictionaries to variables, we can use data structures as arguments to parameters as well.

Here is a small example:

=========================

def summation(v) :
    """summation computes the sum of a list of integers

       parameter: v - a list of ints
       returns: the sum, v[0] + v[1] + ... + v[len(v)-1]
    """
    total = 0
    for value in v :
        total = total + value
    return total


# Test the function:

a = [0, 1, 2, 3, 4]
print summation(a)

raw_input("\n\npress Enter to finish")

==============================


6A.8.2 Foundations: Functions maintain data-structure invariants

The previous section emphasized the importance of writing maintenance functions that contain technical details for updating and maintaining important data structures. We learned in the previous two chapters that data structures usually have important internal properties, called data invariants, that must always be kept true.

For example, the slide puzzle program used a grid to represent the puzzle. The key data invariant for the puzzle is this:

puzzle = [ [15, 14, 13, 12],    # the puzzle.  0 marks the empty space
           [11, 10,  9,  8],
           [ 7,  6,  5,  4],
           [ 3,  2,  1,  0] ]
         # data invariant: puzzle's numbers form a permutation of  0..15
That is, no matter how often puzzle is updated, its cells hold numbers that are exactly some permutation (mixed-up set of) 0 to 15.

A data structure's maintenance functions must maintain the data structure's data invariant. Since the maintenance functions are the only functions that have permission to directly change the data structure, we should always carefully check, when each maintenance function is called and finishes its work, that the data invariant still holds true.

For the slide puzzle in the previous section, it is clear that the printPuzzle function does not alter the puzzle, so it maintains the puzzle's data invariant. When we study the move function, we notice that it alters the puzzle here:

puzzle[empty_space[0]][empty_space[1]] = num
puzzle[i][j] = 0  # the new empty space
Because we know that empty_space is a pair of two numbers that remembers the coordinates of where 0 lives in the puzzle, we can confirm that puzzle's data invariant is kept true by these two commands, which exchange a 0 in one cell with an integer in another cell.

Data invariants are critical to banking applications. Consider one more time the bank-account example and note carefully the data invariant attached to the account's balance:

========================================

# A modelling of one person's bank account.

import random
balance = random.randrange(100000)   # the current balance, in cents
    # data invariant:  balance >= 0

# Maintenance functions for the account:

def withdraw(cash) :
    """withdraw  removes  cash  from  balance.  If cash is greater than balance,
       then only the remaining balance is withdrawn.

       parameter - cash, an int, a cents-only amount
         precondition: cash >= 0
       returns the amount of cash withdrawn from the balance
         postcondition:  0 <= amount_withdrawn <= cash
                         and  amount_withdrawn + balance_new == balance_old
    """
    global balance
    if cash > balance :
        amount_withdrawn = balance
    else :
        amount_withdrawn = cash
    balance = balance - amount_withdrawn
    return amount_withdrawn

def getBalance() :
    """getBalance returns the account's balance

       returns the  balance, in cents
    """
    global balance
    return balance

=========================================================
This account is never allowed to go negative, and a careful analysis of the algebraic knowledge computed by the withdraw function confirms that the data invariant is upheld:
global balance
# assert data invariant:  balance >= 0
if cash > balance :
    # assert: cash > balance
    amount_withdrawn = balance
    # assert: amount_withdrawn = balance
    # implies: amount_withdrawn <= balance
else :
    # assert: not(cash > balance)
    # implies: cash <= balance
    amount_withdrawn = cash
    # assert:  amount_withdrawn <= balance
# assert in both cases:  amount_withdrawn <= balance
balance = balance - amount_withdrawn
# assert: balance >= 0
return amount_withdrawn
This reasoning is absolutely critical to the bank, who does not wish to give away more money that what has been deposited!

Because we allow only the maintenance functions to alter the data structures, we need check the data invariants only within the maintenance functions. This greatly reduces the work we must do to ensure that the entire program, which might be truly huge, operates correctly.


6A.9 Design: Bottom-up, top-down, and modular-programming

We have already learned that functions are ``small parts'' that are useful for
  1. doing one small thing that must be done over and over from different places in a program. This is called bottom-up programming, and the functions are called helper functions.
  2. doing one important thing that must be designed and tested by itself before it is inserted into the rest of the program. This is called top-down programming. Such functions are sometimes called top-down or stepwise-refined functions.
  3. doing one thing to a global variable (data structure) to help keep the global variable correctly updated. This is called modular programming, and the functions are called maintenance functions.
In the ATM program, we saw all three uses of functions:
  1. bottom-up: The errorMessage function was invented because error messages were printed at multiple places in the program.
  2. top-down: The formatDollarsCents function was written to contain the commands that convert a cents amount into a formated dollars-and-cents amount.
  3. modular: The withdraw function was written to update balance each time cash is withdrawn.

All three reasons for coding functions will be used in big programs, and we must look for opportunities to employ them. Most programmers find bottom-up programming is easy to learn (you spot duplicate code or you find yourself copying-and-pasting code so you write a function that holds the duplicate code); modular programming is a bit tougher to learn (you look at how the program alters the main data structure and you write a function containing the commands that do the alterations); and top-down programming is the toughest to do well (you think about what are the ``big steps'' the program must do to solve the problem and you define a function for each big step).

We now do a case study to illustrate how these techniques might be used.


6A.9.1 Case study: Rebuilding the checkbook assistant program

In Chapter 3, we built a checkbook-assistant program that logged a sequence of bank-account deposits and withdrawals, maintained the balance, and printed the ledger when asked.

The program we wrote (Chapter 3, Figure 3?), was acceptable, but if we reread it, we see that there are too many little details in the main program, the same details appear in multiple places, and many of the commands do tiny steps on the ledger data structure. Now that we know about functions, we can redesign the program and do better. (By the way, many useful programs are written twice --- once to understand how to solve the problem, and once to build a good solution!)

Let's progress through the usual stages of program design and implementation.

Stage 1: State behavior

Here is the behavior we expect from the program:
$ python Checkbook2.py

Type request: d(eposit), c(heck), v(iew), q(uit): d
Type transaction amount (ddd.cc): $200.00
Type date of deposit: April 1

Type request: d(eposit), c(heck), v(iew), q(uit): c
Type transaction amount (ddd.cc): $52.69
Type date and to whom check is payable: April 3 BookStore Co.

Type request: d(eposit), c(heck), v(iew), q(uit): v
deposit 200.00 April 1
check 52.69 April 3 BookStore Co.
Current balance is: $147.31

Type request: d(eposit), c(heck), v(iew), q(uit): q

Have a nice day.

Stage 2: Design the data structures

The behaviors suggest that a program (or a person) who logs transactions and keeps a balance would require a variable to remember the current balance and a ledger to remember the transactions. Here is a diagram of how the data structures might look after the first two transactions in the above example:

The diagram suggests that the ledger is a sequence of transactions. We know that either a tuple or a list can be used to make a sequence; for now, we reuse the tuple from Chapter 3, because once a transaction is constructed and added to the ledger, there is no need to index it and alter its contents.

As for the transactions, each transaction might be a single string, or we might make each transaction into a tuple of its own, so that the ledger looks like a tuple of tuples:

ledger = ( (20000, "deposit", "April 1"),
           (5269, "check", "April 3 BookStore Co.") )
Let's do this.

The earlier sections in this chapter suggest that it will be useful to write some functions that maintain the balance and ledger. We will do this, but perhaps it is a bit too soon to know exactly what these functions might be. (We might guess that we will need a function that adds a new transaction to the ledger, and a function that updates the current balance. But what parameters should they use? What kind of answers should they return? Maybe it is too soon to know!)

In a situation where we are unsure which functions to write, we can continue to the next stage of development and later return to writing functions that update the data structure.

Stage 3: Write the algorithm

In Chapter 3, we noted that the accounting program was processing a sequence of user transactions and that there is a pattern for doing this. Here is the algorithm that was proposed in Chapter 3; it is the standard indefinite-iteration pattern:
# here are the data structures:
 balance = 0  # the account's balance starts at 0 cents
 ledger = ()  # the history of transactions starts empty

# here is the algorithm:
processing = True
while processing :
    read the transaction
    if transaction == "q" :
	processing = False
    elif transaction == "c" :
        process a check-writing request
    elif the transaction == "d"
        process a deposit
    elif the transaction == "v" :
        print the contents of the ledger and the balance
    else :
        the transaction is bad, so print an error message
Notice something important: phrases like ``process a check-writing request'' and ``process a deposit'' and ``print the contents of the ledger and the balance'' are describing important computation steps that we will probably want to design, write, and test one at a time. This suggests we might make each of these steps into a function:
processing = True
while processing :
    read the transaction
    if transaction == "q" :
        processing = False
    elif transaction == "c" :
        processCheck()
    elif the transaction == "d"
        processDeposit()
    elif the transaction == "v" :
        printLedger()
    else :
        errorMessage("I don't understand your request; please try again.")
Sometimes we keep all these function calls; sometimes we don't --- it often depends on whether it takes a lot of work or only a little to complete a step.

But let's get to work and refine the step, process a deposit (processDeposit()): Here's how we did it in Chapter 3:

read the amount of the deposit
read the date of the deposit
add the amount of the deposit to the current balance
append the deposit info to the end of the ledger
When we refine the above into Python code, we are confronted with lots of details:
# read the amount of the deposit:
data = raw_input("Type transaction amount (ddd.cc): $")
dollars_cents = data.split(".")
dollars = int(dollars_cents[0])
cents =  int(dollars_cents[1])
amount = (dollars * 100) + cents
# read the date of the deposit:
info = raw_input("Type date of deposit: ")
# add the amount of the deposit to the current balance:
balance = balance + amount
# append the deposit info to the end of the ledger:
... whew! even more commands to write!
Clearly, we should bundle these steps into a function that we write and test separately.

Even so, there are still too many details in the deposit step. First, a function that holds the commands that read the deposit amount would be helpful --- we will need a similar function for the check-writing step, anyway. Also, we see a command that is altering the global variable, balance, and there will be commands that alter the global variable, ledger. These commands are good candidates for bundling into functions that maintain the global data structures.

Before we invent these functions, let's quickly compare the refinement of the deposit step to the refinement of the check-writing step:

read the amount of the check
read the date and payee 
subtract the amount of the check from the current balance
append the check info to the end of the ledger
We see that the same commands will be needed to read the amount of the check as the ones used for reading the amount of the deposit! It is dead clear that we should define a function for reading a dollars-cents amount.

We also see that the last step, that of appending the transaction to the ledger, is a repeat of the last step in the deposit transaction. So, a function should be written for this, also.

Here are the two functions that will be called by both the deposit and check-writing steps:

def readAmount() :
    """readAmount  reads a  dollars.cents  amount from the user and converts it 

       returns: the dollars.cents amount converted into cents-only, an int
    """
    data = raw_input("Type transaction amount (ddd.cc): $")
    dollars_cents = data.split(".")
    dollars = int(dollars_cents[0])
    cents =  int(dollars_cents[1])
    return (dollars * 100) + cents
def logTransaction(kind, amount, message) :
    """logTransaction adds a transaction to the end of the ledger.

       parameters: kind - a string, either "deposit" or "check"
                   amount - an int, the amount of the transaction
                   message - a string, the details about the transaction
    """
    pass # ...we must refine and write this one; for now, we use  pass
The deposit step now looks like this:
amount = readAmount()
info = raw_input("Type date of deposit: 
# add the amount of the deposit to the current balance:
balance = balance + amount
logTransaction("deposit", amount, info)
This looks much better. The coding for writing a check looks almost the same:
amount = readAmount()
info = raw_input("Type date and to whom check is payable: ")
balance = balance - amount
logTransaction("deposit", amount, info)

Now, it is time to apply ``modular programming'': When a data structure (or an important global variable), like balance or ledger, is altered inside the algorithm, we should seriously consider writing a function that holds the details about the alteration and place that function next to the data structure. This helps us see better how the data structure is constructed and maintained.

For the example, we might define this function, which bundles together the changes made by a deposit to the global variables, balance and ledger:

def doDeposit(amount, info) :
    """doDeposit updates the balance and ledger with a deposit transaction.

       parameters: amount - an int, the amount of the deposit
                   info - a string, the details of the transaction
    """
    global balance
    balance = balance + amount  # add the deposit to the balance
    logTransaction("deposit", amount, info)
The deposit step now looks like this; it's even simpler to read:
amount = readAmount()
info = raw_input("Type date of deposit: ")
doDeposit(amount, info)
In the same way, we write a function for check-writing:
def doWithdrawal(amount, info) :
    """doWithdrawal  updates the balance and ledger with a check-writing transaction

        parameters: amount - an int, the amount of the check
                    info - a string, the details of the transaction
    """
    global balance
    balance = balance - amount  # deduct the check from the balance
    logTransaction("check", amount, info)
and the check-writing step looks like this:
amount = readAmount()
info = raw_input("Type date and to whom check is payable: ")
doWithdrawal(amount, info)
This looks pretty, because the function calls are explaining to the human reader the steps required to process a check. (Some people call such readable commands, self documenting code --- there is no need for extra comments to explain what the program is doing.)

Stage 4: Coding and Testing

Here is the program we have written, using the mixture of bottom-up, top-down, and modular-programming techniques:
FIGURE=========================================================

# Checkbook
#   maintains a ledger of checkbook transactions and a running balance
# assumed input: a series of transactions of four forms:
#    (i) Deposits
#    (ii) Check written
#    (iii) View ledger:
#    (iv)  Quit:  
#
# guaranted output: a correct ledger, listing the sequence of deposits
#   and checks, along with the final balance, generated by the  v  command


# The program's data structures (global variables) and the functions that
#   maintain them are listed here first:

ledger = ()  # log of transactions
             # data invariant: remembers all user-supplied transactions
balance = 0  # maintains the balance in cents

def doWithdrawal(amount, info) :
    """doWithdrawal  updates the balance and ledger with a check transaction.

        parameters: amount - an int, the amount of the check
                    info - a string, the details of the transaction
    """
    global balance
    balance = balance - amount  # deduct the check from the balance
    logTransaction("check", amount, info)

def doDeposit(amount, info) :
    """doDeposit updates the balance and ledger with a deposit transaction.

       parameters: amount - an int, the amount of the deposit
                   info - a string, the details of the transaction
    """
    global balance
    balance = balance + amount  # add the deposit to the balance
    logTransaction("deposit", amount, info)

def logTransaction(kind, amount, message) :
    """logTransaction adds a transaction to the end of the ledger.

       parameters: kind - a string, either "deposit" or "check"
                   amount - an int, the amount of the transaction
                   message - a string, the details about the transaction
    """
    global ledger
    formatted_amount = formatDollarsCents(amount)
    info = (kind, formatted_amount, message)
    ledger = ledger + (info,)

def printLedger() :
    """printLedger prints the contents of the ledger and the current balance"""
    global ledger
    for transaction in ledger :
        print transaction
    print "Current balance is:" ,
    print  "$" + formatDollarsCents(balance)

def formatDollarsCents(amount) :
    """formatDollarsCents formats a cents amount into a dollars.cents string

       parameter: amount - a nonegative int, a cents-only amount
       returns: a string, formatted as  dollars.cents.
         (If  amount  is < 0, then the string, 'negative' is returned.)
    """
    if amount >= 0 :
        dollars = amount / 100
        cents = amount % 100
        import string  # load  string  module with helper functions
        answer = "$" + str(dollars) + "." + string.zfill(cents, 2)
    else :
        answer = "negative"
    return answer

# to be continued
=========================================================
FIGURE (concl.)===========================================

# Here are the helper functions for the main program:

def errorMessage(message) :
    """errorMessage  prints an error message.

       parameter: message - a string, the text of the message
    """
    print "Error: " + message
    print "Sorry."

def readAmount() :
    """readAmount  reads a dollars.cents  amount from the user and converts it 

       returns: the dollars.cents amount converted into cents-only, an int
    """
    data = raw_input("Type transaction amount (ddd.cc): $")
    dollars_cents = data.split(".")
    dollars = int(dollars_cents[0])
    cents =  int(dollars_cents[1])
    return (dollars * 100) + cents


# The main program starts here:

processing = True

while processing :

    request = raw_input("\nType request: d(eposit), c(heck), v(iew), q(uit): ")

    if request == "q" :  # Quit
        processing = False

    elif request == "c" : # Check written
        amount = readAmount()
        info = raw_input("Type date and to whom check is payable: ")
	doWithdrawal(amount, info)

    elif request == "d" : # Deposit
        amount = readAmount()
	info = raw_input("Type date of deposit: ")
	doDeposit(amount, info)

    elif request == "v" : # View all transactions
        printLedger()

    else :
        errorMessage("I don't understand your request; please try again.")

raw_input("\nHave a nice day.")

ENDFIGURE====================================================
The program includes a function, formatDollarsCents, that we studied earlier in the chapter.

At this point, we should test the program. If you do this, try to ``break'' the program by typing silly input values and also by writing more checks than what you deposit. It is clear that the program must be improved before we give it to an accountant to use!

Stage 5: Document

The previous Figure has already included commentary for the program, so that you can understand how the data structures and functions operate. But it is also a good idea to check that the codings match the comments that are inserted with them.

Modifying the program

Functions are ``small parts,'' and sometimes a part must be removed and replaced by another. For practice, let's do this.

Just now, the program uses a ledger data-structure that is a tuple of transactions, where each transaction is itself a tuple, e.g.,

ledger : ( ("deposit", 20000, "Apr 1"),
           ("check", 5769, "Apr 3, Bookstore Co.) )
This works fine, but say that we decide to implement the ledger as a list, like this:
ledger : [ ("deposit", 20000, "Apr 1"),
           ("check", 5769, "Apr 3, Bookstore Co.) ]
Now, where must we change the program?

Because we used modular-programming techniques, we have one function, logTransaction, which makes all the updates to the ledger. This is the only function that must be changed in the entire program! We ``unplug'' the existing logTransaction function and replace it with this one:

def logTransaction(kind, amount, message) :
    global ledger
    formatted_amount = formatDollarsCents(amount)
    info = (kind, formatted_amount, message)
    ledger.append(info)   # append  adds a new cell to the end of a list
Then we change the initialization assignment at the top of the program to say
ledger = [ ]
and we are finished!

This little alteration show us how easy it is to alter and improve a function that is built from functions --- we unplug one function and replace it by its improvement.

To Conclude...

Algorithm refinement is often a mix of bottom-up, top-down, and modular programming strategies. If doing all of them at once is overwhelming, then use the ``beginner's strategy'' to programming:

Next, you can go to the ``intermediate strategy'': Finally, you can apply the ``full strategy'': These strategies are a methodical way for learning bottom-up, modular, and top-down programming. (I realize that the full strategy is not exactly top-down. But it is difficult to blindly apply a true top-down strategy without a lot of bottom-up experience!)

The final version of the checkbook accountant is well organized, and in particular, its main program is pretty and can be easily understood by a human. But the list of data structures and functions that precede the program is long and distracting.

In the next chapter, we will learn how to use Python modules to split the above program into two ``big pieces'': one piece will hold the data structures and their maintenance functions, and the other piece will hold the main program and its helper functions.


6A.10 Design: A program creates a special world and functions give the language of that world

The previous case study extracted a collection of functions that talk about the actions we make with bank accounts: we read dollars-cents amounts, we make deposits and withdrawals, we print ledgers, and we format numbers for printing. These functions form a little ``language'' for talking about and working with checking accounts. There is something important going on here:

Every computer program builds its own ``special world'' inside the computer.

For example, a program that helps you play chess builds the world of chess in the computer; a program that does bank-account management builds the world of bank accounts in the computer; the slide-puzzle program built a slide puzzle inside the computer.

Functions define the terminology --- the language --- of actions in the special world.

Each function used in the program is a ``little piece'' of code that makes an action. Each action is a significant concept, because we have bothered to name it!

For these two reasons, extracting and defining functions is not an idle, cosmetic task --- it is a central part of programming, because along with the data structures, the functions define the program's special world and give us the language to talk about how that world operates.

Many so-called domain-specific programming languages grew out of functions extracted from programs that were built to operate in a specific problem domain --- The family of functions became the basis of a new programming language!

After you complete this course, you will likely apply your computing skills in other courses. Say that you take a biology class, and you must write computer programs to do sequence matching of data to gene patterns. What is the ``language'' of genes and matching? You will uncover the answer by constructing your programs and naming the functions used in those programs.

At this point, you are ready to read Chapter 6 of Dawson's text. Study carefully his tic-tac-toe example, and try to deduce which of his functions were created from bottom-up, top-down, and modular programming styles. Think about whether the functions he chose define a language for talking about and playing tic-tac-toe.


6A.11 Semantics of functions: local namespaces

In the earlier sections of this chapter, we encountered diagrams that showed what happens inside the computer when functions are called. The diagrams were helpful but informal. Now, we study a more precise description of what happens when functions are defined and called. Here is a quick review of the semantics of functions:

Private namespaces are a simple and natural idea, but they are profound and generated a revolution in computer programming when they first appeared in the late 1950's. A function's private namespace is also known as an activation record.

Here is a tricky example that we use to illustrate functions, parameters, global variables, and private namespaces:

FIGURE=========================================================

# program  Test  illustrates features of functions:
                      #Line:
a = 2                 #3

def f(x, y) :         #5
    global a          #6
    a = g(x[0] * y)   #7

def g(y) :            #9
    z = 6             #10
    return y * z      #11

# The program starts here:
b = [3,4]             #14
f(b,4+1)              #15
print a               #16

ENDFIGURE============================================================
Read the program and guess what it prints.

When this program starts, computer storage looks like this:


The Python interpreter has an instruction counter (i.c.) to remember the next command to execute, and it also has a namespace variable (n.s.) to remember the namespace to use. Storage is organized into a local-namespace area, and a global-namespace/heap area.

After the command at Line 3 does its work, initializing a to 2, functions f and g are saved in Test's namespace. Only the initial instruction numbers of the functions are saved.

Next, Line 14 constructs a list in the heap and saves its address in the variable cell for b:


Line 15 calls f. These steps are undertaken:

  1. The two parameters are computed to their values: b is found in the namespace, and it computes to addr1; 4+1 computes to 5.
  2. f is found in the namespace, and i.c. is reset to instruction 6.
  3. A private namespace for f is constructed in the area for local namespaces; the namespace holds the names of parameters x and y, and the two arguments are placed in those cells. An extra name, global, is saved in the namespace to remember the namespace in which f was originally defined. Variable ns is set to f's namespace.
Here is the configuration:

The Python interpreter has paused the execution at Line 15 and will execute the function at line 6. The namespace used is f's.

Line 6 adds a to the namespace, but a cell is not created; instead, a is marked as belonging to the global namespace (Test):


Line 7 starts an assignment to a. First, since a is already present in f's namespace, no cell is constructed.

Next, the expression, g(x[0] * y), must be computed, so that it can be assigned to a. This means x[0] * y must be computed. First, x is found in the namespace; it computes to addr1. This means that cell 0 at addr1 is found and its value, 3, is returned. Next, y's value, 5, is found. Finally, 3*5 computes to 15.

The next step pauses execution within f and starts execution at g. A namespace for g is constructed in the local namespace area, and the argument, 15, is placed in y's cell there. (This is a different y than the one in f's namespace!) Both i.c. and n.s. are reset:


Line 10 constructs a cell in the current namespace (g's):

Line 11 says to compute y*z, which is 90, and return it to function f. At this point, g's namespace is erased, and both i.c. and n.s. are reset to their values just before the call to g:

To finish the assignment at Line 7, 90 is assigned to a's cell. In the current namespace, a is marked global, so the cell for a in namespace Test receives 90. At this point, function f is finished, so its namespace is erased, and both i.c. and n.s. are reset:

The print command prints 90.


6A.12 Summary

Syntax and semantics of functions

A function definition has the form,
def NAME(PARAMETERS) :
    DOC_STRING
    COMMANDs
where The semantics of a function definition is that the function's name, its parameters, and its commands are saved in the current namesapce.

A function call (invocation) has the format,

NAME(ARGUMENTS)
where The semantics of a function call operates as follows:
  1. The arguments are computed to answers, one by one, from left to right.
  2. The function's name and its body are located in the namespace.
  3. Execution at the position of the function call is paused.
  4. The arguments are assigned, one by one, to the parameters listed in the function's definition.
  5. The commands within the function are executed.
  6. When the commands finish, execution resumes at the position where the function was called.
When a return EXPRESSION command is executed within the function's body, the expression part is computed to an answer, the function immediately terminates, and the answer is inserted exactly in the position where the function was called.

A function can also be called with keyword arguments.

A function, F's documentation string can be printed with the command, print F.__doc__

Functions can be written and testing separately, using interactive testing:

  1. Place the function, fred, in a file by itself, say, Test.py.
  2. Open a command window and start the Python interpreter interactively:
    $python -i Test.py
    
  3. Interactively test the function:
    >>> fred(..arguments...)
    
    This executes the function, just as if it was called from within a program.
  4. If you change the coding of fred, you can retest it by stopping the Python interpreter and starting over.

There are three important uses of functions:

  1. doing one small thing that must be done over and over from different places in a program. This is called bottom-up programming.
  2. doing one important thing that must be designed and tested by itself before it is inserted into the rest of the program. This is called top-down programming.
  3. doing one thing to a global variable (data structure) to help keep the global variable correctly updated. This is called modular programming.