Copyright © 2006 David Schmidt

Chapter 6B:
Building programs from ``big parts'': Modules


6B.1 Module format
6B.2 Program modules: Dividing a program into stages
    6B.2.1 Semantics of import
    6B.2.2 Case Study: The Yoda-sentence program assembled from two modules
6B.3 Model modules: A data structure and its maintenance functions
6B.4 Foundations: Module invariants
6B.5 Design Study: A card-game
6B.6 Technical Issues about Modules
    6B.6.1 Forms of importation
    6B.6.2 The search path
6B.7 Design: Software architectures
6B.8 Semantics of modules: Multiple global namespaces
6B.9 Summary


In the previous chapter, we learned how to invent the ``little parts'' of programming by writing functions. This chapter, we learn about the ``big parts'' --- modules.

Subassemblies

Almost all manufactured products (televisions, toasters, cars, etc.) are constructed from small parts that are assembled into ``big parts'' --- subassemblies --- which are connected together to make the final product. One advantage of using subassemblies is efficiency: the subassemblies are built separately, by distinct groups of people, each of whom become expert at building their own subassembly.

Another reason for subassemblies is design: it is easy to update and improve a product by merely disconnecting a subassembly and replacing it by another. Auto companies do this all the time when they issue a new ``model'' of car. Yet another reason is economy: if the subassemblies are built so that they easiler connect to other subassemblies, then the same subassembly can be used in distinct products. Again, the automakers are experts at using the same engine assembly in many different versions of car.

Subassemblies are used outside of manufacturing, too. For example, a book is organized into chapters, and a video game has levels. But there are even more interesting forms: Any series of books or video games (Mario Brothers and Star Trek, to mention two examples) reuse the same characters with the same personalities in each book or game. The characters are ``literary subassemblies'' that are ``connected'' to the books and games. Working with a fixed cast of characters is efficient, promotes a uniform design, and lets us reuse actors who people enjoy.

A book series's collection of characters, locales, tools, and terminology (think again of Star Trek or Mario Brothers) form a specific world in which new books and games are easily invented and enjoyed. Such a world is called a domain, and the people, things, and concepts in the domain form a domain-specific language. Domain-specific languages are a powerful tool for production of quality cars, televisions, books, and games. And, the idea also applies to program construction!

Subassemblies in software: Modules

If books and games have subassemblies, then surely computer programs do also. Indeed, large programs (operating systems, web browsers, networks) and many small programs are divided into subassemblies, called modules.

Modules are used for efficiency reasons: distinct teams build distinct modules, speeding the development and letting each team do a good job on their specific part of the program.

Modules are also used for design reasons: When a module of the program must be repaired or improved, the entire module is removed and replaced with a newly written one.

Modules are also used for economy: Modules are often written so that they can be used within many different programs. For example, a module that codes the layout of a chess board and the behaviors of chess pieces can be used in various chess-playing programs.

Finally, when a program solves a problem in a standard problem area (domain), it is useful to collect together into a module the functions --- the ``little pieces'' --- that helped solve the problem. Such a module is the starting point of a domain-specific programming language for program building in the problem domain.


6B.1 Module format

A module is a file of Python commands. Modules connect (link) to one another and call each other's functions. A complex program is assembled from a collection of such modules that call each other's functions.

A module is constructed so that it has

  1. global data structures
  2. start-up commands
  3. functions that can be called/contacted by other modules to connect to this one
When a module lacks the third item, that is, it has no functions for external contact, it becomes an ordinary program. (The start-up commands are the program's commands.) In this sense, we have been writing modules since Chapter 2 up to the previous chapter. We might call such a module a program module.

Another form of module lacks start-up commands, that is, it does computation only when its functions are called/contacted by other modules. We saw examples of this in the previous chapter: a bank account and its maintenance functions, a telephone book and its maintenance functions, and a slide puzzle and its maintenance functions. Each of these examples models a physical object, and for this reason, such a module is called a model module. We saw how an ATM program module might call the functions of the bank-account module module to permit cash withdrawals. In this chapter, we learn how to place the program module in one file and the bank-account model in another file, and link them together.


6B.2 Program modules: Dividing a program into stages

In Chapter 4, we saw how to read a line of text and divide it into words. Then we saw how to rearrange the words into ``Yoda sentences.'' We developed the resulting program in two stages: The first program, Words.py, read a sentence as input and produced as its output a tuple that contains all the words in the input sentence. The second program, Rearrange.py, rearranged the tuple's words into a ``Yoda sentence.''

It is sensible to connect the two programs together in a sequence, like this:


Each program is in fact a program module, and we can connect together the two program modules so that the second one uses the results from the first. This approach is the oldest known use of modules, and we use it as a motivating example.

Let's begin with two tiny program modules and see how to connect them in sequence. Here is a program module, Square:

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

# A module begins with a documentation string:

"""Square computes the square of an int

   assumed input: an int
   guaranteed output: the int's value squared, saved in variable  sq
"""
input = int(raw_input("Please type an int: "))
sq = input * input

====================================
The program module has no functions that can be called by other modules, and its only ``data structures'' are its variables, input and sq.

Now, start the Python interpreter, and in response to the prompt, >>>, type the following:

>>> import Square
You have just demanded that module Square be imported into the computer, and you will see this in the interpreter's window:
Please type an int: 
This shows that Square has been copied (loaded) into primary storage and started. Say that you type 3 and press Enter. You will see merely
>>>
Although the program module has finished all its instructions its namespace (that is, its variables and their values) is still living in computer storage, and you can access it. Type this:
>>> Square.sq
and you will see printed, 9 --- you have just demanded the value of variable sq from within the namespace of module Square. If you like, you can compute with the value:
>>> j = Square.sq
>>> j * j
This prints 81. In this simple way, you have ``contacted'' the ``subassembly'' (module) for information that you used to compute a multiplication.

We can write a second program module that does what we did with our fingers and the keyboard; here it is:

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

"""Fourth  uses module Square to compute the fourth power of an int."""

import Square
j = Square.sq   # contact Square to learn the value of  sq
print j * j

raw_input("\npress Enter to finish")

=================================================
Here is what you see when you start this program:
$python Fourth.py
Please type an int: 3
81
This shows that Fourth was started; it imported Square; the latter its work; and Fourth extracted the value of sq from Square's namespace and finished the computation.

Notice if you write the following in program Fourth:

import Square
j = Square.sq
import Square
this does not execute Square twice! (Try it and see.) A module is not an ``oversized function'' --- it is a subassembly that is loaded and started just once. (Later, we will learn how to place functions in a module, import the module once, and then call the module's functions over and over.)


6B.2.1 Semantics of import

Let's study what happens inside the computer when this example executes. When we start, python Fourth.py, we have this configuration:


Program module Fourth.py has been loaded into computer storage, and it has just started execution. The instruction counter (i.c.) marks the first command in Fourth to execute, and the namespace pointer, n.s., remembers that Fourth's namespace is in use.

The first command, import Square, pauses execution within Fourth, loads module Square into storage, and constructs a new namespace for the new module:


The computer's instruction counter (i.c.) resets to the commands within Square, and the namespace variable (n.s.) resets to Square's namespace.

Square's (start-up) commands execute --- this reads the user's input and calculates its square:


The computer constructs variables input and sq within Square's namespace because n.s. correctly remembers that commands in Square are executing.

Once program module Square finishes its start-up commands, the instruction counter and namespace pointer reset to Fourth, but Square's namespace remains. In addition, a variable named Square is constructed within Fourth's namespace --- since Fourth imported Square, it is linked to it, meaning that it can contact the module and look into its namespace:


So, the command, j = Square.sq, computes as follows:

  1. a cell is created for j in Fourth's namespace;
  2. the value of Square is fetched --- it is the address of module Square's namespace
  3. within Square's namespace, variable sq is located and its value, 16, is fetched
  4. the value, 16, is assigned to j's cell.
In this way, program module Fourth contacted module Square.

If you wish, you can think of a module's namespace as a dictionary data structure. Indeed, it is only for historical reasons that the lookup of sq in Square is written Square.sq (and not as Square[sq]).

When Fourth finishes its start-up commands, the configuration looks like this:


Both namespaces rest in storage, waiting for contact from other modules.


6B.2.2 Case Study: The Yoda-sentence program assembled from two modules

Now we can assemble the program that constructed Yoda sentences. When you started the program in Chapter 4, you saw behavior like this:
$ python Yoda.py
Type a sentence: take the right turn

Yoda's sentence:
 right the take turn!
We assemble the Yoda program from its two program modules; the first reads a sentence a user types and constructs a tuple of the sentence's words, and the second module uses the tuple of words to build a Yoda sentence.

Here is the Words module, repeated verbatim from Chapter 4:

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

"""Words  extracts the words from a line of text.

   assumed input: a sentence typed as a single line of text
   guaranteed output: the words, listed in the order that
     they appeared in the sentence, saved in variable,  tuple.
"""
separators = (".", ",", ";", ":", "-", "?", "!", " ")
sentence = raw_input("Type a sentence: ")

tuple = ()   # holds the tuple of words we collect
next_word = ""  # holds the characters of each word that we collect

for char in sentence :
    # invariant: all chars examined so far are grouped into words in  tuple
    #   along with the word we are collecting into  next_word
    if  char in separators :    # have we reached the end of a word ?
        if  next_word != "" :   # have we collected a nonempty word ?
	    tuple = tuple + (next_word,)  # add  next_word  to the tuple
	    next_word = ""      # prepare to collect another word
    else :
        next_word = next_word + char  # add  char  to the word we are building

if next_word != "" :  # oops --- did sentence end without closing punctuation?
    tuple = tuple + (next_word,)  

# print tuple   # printing the output from the first step is optional

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

Next, here is a minor rewrite of module, Rearrange, which imports module Words and then contacts it:

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

""" Rearrange  builds a ``Yoda'' sentence, that is, a sentence whose word
    order is changed.

    assumed input: the output from module Words --- a tuple of words
    guaranteed output: the words rearranged into a Yoda sentence
"""
import Words  # load and link to the module, Words.py
tuple = Words.tuple  # contact module Words to learn the value of its  tuple

import random   # load and link to the random-number-generator module

yoda_sentence = ""  # the sentence we will build
while  len(tuple) != 0 :
    # invariant:  the words extracted from tuple are in  yoda_sentence
    # print tuple, yoda_sentence  # print trace info
    choice = random.randrange(len(tuple))   # choose a word to extract
    yoda_sentence = yoda_sentence + " " + tuple[choice]
    tuple = tuple[:choice] + tuple[choice+1:]  # rebuild tuple without the choice   

print "\nYoda's sentence:"
print  yoda_sentence + "!"

raw_input("\npress Enter to finish")

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

The linking and contacting are done by the first two commands: Words is imported, which makes it compute a tuple of words. Next, the value of tuple from Word's namespace is extracted, via

tuple = Words.tuple
and saved in a new variable, also named tuple, in Rearrange's namespace.

Then random is imported. (Module random is already written and comes with the Python interpreter.) Module random contains functions that can be contacted to return random numbers. One of the these functions, randrange, is called by Fourth.

Here is a picture of the software architecture of the program we just assembled:


The solid arrows show that module Rearrange imports both Words and random; the dashed arrows show the direction in which information travels from one module to another. Such diagrammatic ``blueprints'' are commonly used by software architects to design and document large programs.

Because the program's information travels primarily in a sequence, from the input keyboard to one module to the next to the output display, the software architecture is called sequential.


6B.3 Model modules: A data structure and its maintenance functions

The previous chapter emphasized that functions are useful for maintaining a data structure and the functions that do the maintenance should be placed with the data structure. This is exactly the principle behind a model module.

A model module is a data structure and a collection of functions that maintain the data structure. A model has no significant start-up commands, and it is not intended to compute all alone. Instead, it assists other modules with their computations: the other modules call the functions of the model module for assistannce.

Model modules are often used to ``model'' a physical object, like a gameboard or a telephone book or a collection of bank accounts (``cashboxes''). A data structure represents the physical object, and maintenance functions code the actions that one does with the object.

A model module is often considered the ``brain'' of a computer program, because it remembers the information needed to solve a problem. We now review three examples of model modules encountered in the previous chapter.

Telephone book

Here is the telephone book example from the previous chapter; when it is saved in a file by itself, it is a model module:

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

"""TelephoneBook :  It maps a person's name to their phone number,
   e.g.,  tel_book={"Jane Sprat": 4098, "Jake Jones": 4139, "Lana Lang": 2135},
   and it contains maintenance functions for insert, finding, and deleting
   entries.
"""

tel_book = { }  # the data structure

# 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 
       returns: the phone number for name.  If  name is not in the book,
         -1  is returned
    """
    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
    """
    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."""
    global tel_book
    if name in tel_book :
        del tel_book[name]

ENDFIGURE=================================================
The module ``models'' a telephone book inside the computer. It has a data structure and functions that can be contacted by other modules but no start-up commands that compute on their own. A model module is meant to assist other modules.

Here is how another module might use the model module:

import TelephoneBook
 . . .
someone = raw_input("Type name of person: ")
number = TelephoneBook.lookup(someone)
print someone, "has phone number ", number
 . . .
The a function saved within a module is referenced by stating the module name, then a dot, then the function name (e.g., TelephoneBook.lookup(...). This contacts the module and calls its lookup function, which operates with its own namespace and the namespace of the module in which it is embedded. The function is an entry point to the data structure held within the module.

We can use the telephone-book model module in an application that lets a person insert names into a telephone book and do lookups from the book. The module we will write ``controls'' the actions that a human takes upon the telephone book, and for this reason it is sometimes called a controller module. Here is the algorithm for the controller module:

while True
    read a command from the user
    if the command is ``insert'',
       then insertNewPerson into the telephone book
    else if the command is ``lookup'',
       then lookupPerson in the telephone book
    else error
We can convert the algorithm into Python code, where the phrases, insertNewPerson and lookupPerson become calls to functions that use the telephone-book module. Here is the program module based on the algorithm, where the two helper functions are listed first:
FIGURE====================================================

# UseTelBook  helps a human use an electronic phone book.

import TelephoneBook 

# Helper functions for the controller algorithm:
def insertNewPerson() :
    """insertNewPerson  requests a name and number from 
       the user and inserts them into the phone book.
    """
    name = raw_input("Please type a new name: ")
    num = int(raw_input("Please type their phone number: "))
    TelephoneBook.insert(name,num)

def lookupPerson() :
    """lookupPerson  requests a name and finds that 
       name in the phone book.
    """
    name = raw_input("Please type a name to lookup: ")
    print "The phone number for", name, "is", TelephoneBook.lookup(name)

### The controller algorithm starts here:
while True :
    request = raw_input("Type  i(nsert) or  l(ookup): ")
    if request[0] == "i" :
        insertNewPerson()
    elif request[0] == "l" :
        lookupPerson()
    else :
        print "Invalid request"

ENDFIGURE======================================================
The software architecture looks like this:

This form of architecture, where the model module is the ``brain'' of the program and another module controls the actions that a human makes on the model, is called a model-view-controller (MVC) architecture.

The ``view assembly'' in the architecture is the computer keyboard (``input view'') and the display screen (``output view''), so called because they give the human a ``view'' of what the model is and what it knows.

The controller module acts like the controls on the panel of a radio or televison --- it provides the human a means of adjusting the model. The adjustments are transmitted via the input view and results are displayed in the output view.

Since the application is divided into modules, it is straightforward to ``unplug'' one module and replace it by an alternate version. For example, perhaps we decide the change the model module so that the telephone book is modeled by a list of name,number pairs rather than by a dictionary --- We merely replace the existing module, TelephoneBook.py, by this one:

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

# TelephoneBook   holds the data structure and maintenance functions
#  for a telephone book.

# The data structure is a list of  (name,number)  pairs
# Example:  book = [("Mary Smith", 3453), ("Jake Jones", 2458)]
book = []   # the book starts empty

# Here are two helper functions:

def printBook() :
    """printBook  prints the entire contents of the phone book"""
    global book
    for entry in book :
        print entry[0] + ":",  entry[1]

def inBook(name) :
    """inBook checks if  name  is already in the phone book.

       parameter: name, a string - a person's name
       returns: the index number of where the name is found in the book;
         returns -1, if the name is not in the book
    """
    global book
    answer = -1
    index = 0
    for entry in book :
        if entry[0] == name :
            answer = index
            break
        else :
            index = index + 1
    return answer

# Here are the functions used by the other modules:

def insert(name, number) :
    """addPerson  adds a new  name and number to the phone book, 
       provided that the name is not already in the book.

       parameters: name - a string, the person's name
                   number - an int, the four-digit phone number
    """
    global book
    if  inBook(name) == -1 :  # name not in the book?
        book = book + [(name,number)]
    else :
        print "error: " + name + " is already in the book"

def lookup(name) :
    """lookup searches the phone book for name.

       parameter: name - a string, the person's name
       returns: the phone number, an int, for the person
         (if the name isn't in the book,  -1 is returned)
    """
    global book
    number = -1
    for entry in book :
        if entry[0] == name :
            number = entry[1]
            break
    return number

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

       parameter: name - the name to be removed
    """
    global book
    where = inBook(name)
    if where != -1 :
        book = book[:where] + book[where+1:]

========================================
Because the module's functions have the same name, the same parameter structure, and the same behavior, the new module can replace the old one. Note that the new module has some additional functions, but this causes no trouble for the program that imports and uses the module.

Bank database and ATM

The previous chapter developed an ATM controller that contacted a bank's database to withdraw cash. The bank's database is almost certainly a separate module and likely resides on a computer different from the ATM. Here is the database, formatted as a module, and saved in the file, BankDatabase.py:
========================================

# 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. 

# The bank's main office uses these functions to create new accounts and
# deposit money into them:

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
    """
    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

=========================================================
And here is the ATM controller that lets the human use the database:
FIGURE===================================================

# ATM  lets a human withdraw cash from an automated teller machine.
# Inputs: the account number on the person's bank card
#         the dollars and cents amount to withdraw
# Outputs: the amount of money withdrawn (and also, the cash!)
#          and the new account balance

import BankDatabase  # connect this program to the bank's program

##### Some helper functions that reduce copy-and-paste:

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 = BankDatabase.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 = BankDatabase.getBalance(account)
    print "Balance is", formatDollarsCents(new_balance)

raw_input("\nTransaction completed")

ENDFIGURE================================================
There are several advantages to organizing the bank's database and its ATMs into modules: The software architecture of the ATM example is again in the model-view-controller style, where the BankDatabase is the model and the various ATMs are controllers that are connected to physical ATM machines (the views).

Slide puzzle model

Any interactive game is organized so that the game board and its playing pieces are written in one module and the rules of the game and the interaction by the human players is coded in another module.

The previous chapter introduced a simple board game --- the slide puzzle. As an exercise, we can define a slide-puzzle module, which contains the data-structure representation of the puzzle and its key maintenance functions:

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

# SlidePuzzle  models a slide puzzle holding 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 moves 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 toString() :
    """toString builds a string that shows the puzzle's contents.
    
       returns: the string
    """
    global puzzle
    import string
    answer = ""
    for row in puzzle :
        for piece in row :
            if piece == 0 :  # empty space ?
                text = "__"
            else :
                text = string.zfill(square, 2)
            answer = answer + text + " "
        answer = answer + "\n"
    return answer

=============================================================
This module is typical for gameboards: It defines the game board and initializes it with its squares and playing pieces. It contains a maintenance function, move, that does the mechanics of moving a playing piece on the board. It also contains a function, toString that returns a string representation of the board for printing.

Here is how we use SlidePuzzle.py to build a simple controller module for the game board:

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

import Puzzle

while True :
    print Puzzle.toString()
    num = int(raw_input("Type number of piece to move: "))
    OK = Puzzle.move(num)   # try to move the piece showing  num
    if not OK :
        print "Illegal move --- try again"

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

One big advantage of separating the slide-puzzle module from the rest of the game is that we can revise the controller module and especially the puzzle's view (say, by writing a GUI --- graphical user interface) without altering the puzzle itself. The puzzle is the ``brain'' of the game.


6B.4 Foundations: Module invariants

The previous two chapters made these two points about data structures and functions:
  1. Every mutable data structure should be documented with its data invariant, that is, the key algebraic properties that describe the values kept in the structure.
  2. Every function that alters the data structure must maintain the data invariant.
When we place a data structure and its maintenance functions together in a module, we can better understand the data structure's data invaraiant and we can better ensure that all updates to the data structure preserves its data invariant.

A data invariant for the data structure in a module is sometimes called the module invariant.

Please review the BankAccount module and the SlidePuzzle modules in the previous section. Both have data structures that are documented with important data invariants. Next, review the discussion in the previous chapter, which explained why the BankAccount's withdraw function maintained the data invariant for the balance. And, review the discussion there that justifies why the move function maintains the puzzle's data invariant.

When we collect a data structure and its maintenance functions within one module, we can enforce the data (module) invariant entirely within the one module. (This assumes all other modules contact this one via its functions only.) For this strong reason, we try to employ model modules with module invariants as often as possible.


6B.5 Design Study: A card-game

We now employ model modules in the design of an interactive card came.

When we play a card game, we do so with a deck of cards. Most card games use a ``dealer,'' who is the person who gives the cards, one by one to the players. (When you play solitaire, you are both the ``player'' and the ''dealer.'')

Let's design a basic card game, where we model the deck of cards inside the computer, and we write a dealer that gives the human user cards one at a time. (We won't bother here to make the dealer enforce the rules of any particular game; the dealer will give cards to the human until the human says, ``stop.'')

Behavior

Based on the above description, here is the kind of behavior our program should have:
$ python Cards\Main.py

Your hand is:
[]

Would you like another card (y or n)? y

Your hand is:
[(5, 'diamonds')]

Would you like another card (y or n)? y

Your hand is:
[(5, 'diamonds'), (7, 'spades')]

Would you like another card (y or n)? n
That is, the program repeatedly shows the user her hand of cards and asks if another card is desired. To model this behavior, we will build a card deck and an algorithm that extracts cards from the deck and ``gives'' them to the user. The algorithm that extracts cards is the ``dealer.''

Module and algorithm structure

Here is a diagram of the program we must build:

The CardDeck module models a deck of cards; the Dealer holds the algorithm that interacts with the user and the CardDeck. The Dealer's algorithm is simple and will look like this:
while  not yet finished :
    show the user her current hand of cards
    ask the user if she wants one more card
    if yes :
        get one more card from the CardDeck
        add the card to the user's hand
    else (no) :
        we are finished, so quit the loop

Coding and testing the CardDeck module

A real card deck is a collection of individual playing cards, and we must model this arrangement within the computer. Each card has a count (ace, 2, 3, ..., 10, jack, queen, king) and a suit (spades, hearts, diamonds, spots), so a computerized card is a pair, (COUNT, SUIT).

A deck is a collection of cards, which we model, say, as a list of computerized cards:

deck = [ (ace, spades), (2, spades), ..., (king, spades), (ace, hearts), ... ]
We should write an initialize function that generates the cards and places them in deck.

When we study the algorithm for the Dealer, we see that the only operation that the Dealer makes on the CardDeck is dealing a card. Let's call this operation getCard.

Here is what we have designed so far:

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

"""CardDeck models a deck of playing cards, where each card is a pair
     of the form, (spots, suit).  Examples:  ("ace","spades")  or (9,"hearts).

   Operations: initialize (constructs a new deck of cards),
               getCard (extracts and returns a card from the deck)
"""
deck = []  # the deck of cards, where each card is a pair, (count, suit)

def initialize() :
    """initialize  generates 52 cards and adds them to the deck"""
    pass # we must code this

def getCard() :
    """getCard  deals a card from the deck

       returns: the card dealt - it is a pair of the form, (count, suit).
    """
    pass # we must code this

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

The initialize function must insert 13 spades, 13 hearts, 13 diamonds, and 13 clubs into the deck. It is best to write a helper function, add, that inserts one suit of cards and call the function four times, like this:

def initialize() :
    add("spades")
    add("hearts")
    add("diamonds")
    add("clubs")
The add function will merely count, "ace" 2, 3, 4, ..., 10, "jack", "queen", "king" add to the deck one card of each count for each suit. We'll study its coding a bit later. The cards must also be shuffled; this is done with the help of a function from the random module:
import random
random.shuffle(deck) # mix the cards

The getCard function merely removes a card from the shuffled deck.

Here is what we have coded for the CardDeck:

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

"""CardDeck models a deck of playing cards, where each card is a pair
     of the form, (count, suit).  Examples: ("ace","spaces") and (9,"hearts").

   Operations: initialize (constructs a new deck of cards),
               getCard (extracts and returns a card from the deck)
"""
deck = []  # the deck of cards, where each card is a pair, (count, suit)
           # data invariant: deck holds a subset of 52 unique cards, shuffled

def initialize() :
    """initialize  generates 52 unique cards and assigns them to the deck"""
    global deck  # so, we are obligated to maintain  deck's  invariant!
    add("spades")
    add("hearts")
    add("diamonds")
    add("clubs")
    import random
    random.shuffle(deck) # mix the cards
    # at this point,  deck's  invariant is maintained

def getCard() :
    """getCard  deals a card from the deck.
       
         precondition:  deck  is nonempty
       returns: the card dealt - it is a pair of the form, (count, suit).
         postcondition:  deck_new.append(card) == deck_old
    """
    global deck  # we must maintain  deck's  invariant
    if (len(deck) == 0) :
        print "CardDeck error: no more cards to get"
        card = ()
    else :
        card = deck.pop()  # removes card from the end of deck and
                           # makes the deck smaller by one
    # at this point,  deck's  data invariant is maintained
    return card

def add(suit) :
    """add  is a helper method that inserts one suit of cards into the card deck

       parameter: suit - a string, the name of the suit ("clubs" or "diamonds"
         or "spades" or "hearts")
    """
    global deck  # this function does _not_ maintain the data invariant,
                 # but it is called _only_ within  initialize,  which does!
    deck.append(("ace", suit))
    for i in range(2, 11) :   # this counts from 2 through 10
        deck.append((i,suit))
    deck.append(("jack", suit))
    deck.append(("queen", suit))
    deck.append(("king", suit))

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

The functions use two new Python tricks:

Notice also the comments that explain when the functions maintain deck's data invariant. Functions initialize and getCard are obligated to do so, but since add is a helper function called only by initialize, the data invariant may temporarily lapse when it executes and finishes.

We might test the module interactively the same way we tested functions:

$python -i CardDeck.py
>>> initialize()

>>> for i in range(55) :
...     next_card = getCard()
...     print next_card
or, we might interactively import and test the module, like a controller module would do:
$python
>>> import CardDeck

>>> CardDeck.initialize()

>>> for i in range(55) :
...     next_card = CardDeck.getCard()
...     print next_card
The previous tests empty the card deck and show whether getCard is indeed returning all the cards in the deck in a random ordering.

Exercise

Often it is helpful to know when a card deck is empty. Add to module CardDeck this function:
def isEmpty() :
    """isEmpty checks if there are cards left in the deck

       returns True, if the deck has more cards;
       returns False, if the deck is empty
    """
    . . .
Test the function like this:
for i in range(110) :
    if CardDeck.isEmpty() :
        print "deck empty --- time to refill"
        CardDeck.initialize()  # refill the deck
    else :
        print CardDeck.getCard()

The Dealer module

The Dealer module is the ``controller,'' which imports the CardDeck and gives its cards to the user. Here is its algorithm:
while  not yet finished :
    show the user her current deck of cards
    ask the user if she wants one more card
    if yes :
        get one more card from the CardDeck
        add the card to the user's hand
    else (no) :
        we are finished, so we quit the loop
When we refine this algorithm, we see that we must invent a data structure that holds the cards that are already given to the user (so that we can print the cards at each iteration of the loop). We can use a list to model the player's hand of cards. Here is a quick Python coding of the algorithm:
FIGURE=============================================

"""Dealer controls a CardDeck so that a human can take cards one at a time."""

import CardDeck  # the data structure module

CardDeck.initialize()

hand = []  # holds the cards that the human has requested

print "I will deal you a hand of cards!\n"
more_cards_needed = True

while more_cards_needed :
    print "Your hand is:"
    print hand
    request = raw_input("\nWould you like another card (y or n)? ")
    if request == "n" :  
        more_cards_needed = False
    elif request == "y" :
	card = CardDeck.getCard()
	hand.append(card)
    else :
        print "Sorry--bad request; try again"

raw_input("\npress Enter to finish")

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

Although Dealer.py is small, we note that the list, hand, of the user's cards is in fact another data structure that should be extracted and placed in its own module. For practice, let's do so.

Here is the revised Dealer module and another ``model'' module, called HandOfCards:

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

"""Dealer2 controls a CardDeck so that a human can take cards one at a time."""

import CardDeck  # the deck of cards
import HandOfCards # the user's hand of cards

CardDeck.initialize()

print "I will deal you a hand of cards!\n"
more_cards_needed = True

while more_cards_needed :
    print "Your hand is:"
    HandOfCards.showCards()
    request = raw_input("\nWould you like another card (y or n)? ")
    if request == "n" :
        more_cards_needed = False
    elif request == "y" :
        HandOfCards.addCard(CardDeck.getCard())
    else :
        print "Sorry--bad request; try again"

raw_input("\npress Enter to finish")

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

"""HandOfCards holds the cards requested by the user.

   Operations:  showCards - prints the contents of the hand
                addCard - adds one more card to the user's hand
"""
hand = []  # holds the cards that the human has requested
           # invariant: holds a subset of the 52 possible cards

def toString() :
    """toString returns a representation of the hand"""
    global hand
    answer = ""
    for card in hand :
        answer = answer + (card[0] + " of " + card[1]) + "\n"
    return answer

def addCard(card) :
    """addCard adds one more card to the user's hand.

       parameter card - the card to be added
    """
    global hand
    hand.append(card)

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

The HandOfCards module is tiny, but it was good to extract because it streamlined the controller module, Dealer2. Here is a diagram of what we have built:


Remember that the solid lines represent dependency (here, Dealer2 depends on CardDeck and HandOfCards because it calls their functions) and the dotted lines represent data flow (a user request causes a card to travel from CardDeck to HandOfCards and then to the display due to printing). controller

Documentation

For multi-module programs, comments serve two purposes:

As always, we can import a module and then read its documentation:
import M
 ...
print M.__doc__
if module M contains a function, f, we can of course print its documentation, too:
print M.f.__doc__

Exercises

Improve the card-game program so that the Dealer2 module enforces the rules of some specific card game, e.g., ``21'' (``blackjack''), where the player tries to collect a hand of cards whose total counts come as close as possible to 21 without exceeding it. (Recall that face cards have a count of 10. For simplicity, assume an ace has count of 1.)

Next, write a ComputerCardPlayer module to which the Dealer2 also deals cards so that the ComputerCardPlayer competes against the program's user to obtain a higher score at playing 21. (Make the computer player ``hold'' (stop asking for cards) when the computer player has a total count of 17 or more.


6B.6 Technical Issues about Modules

When testing a module you have imported, here are some new Python expressions you can use:
dir()  # lists the bindings in the current namespace

dir(M) # lists the bindings in the namespace of module, M

reload(M) # reimports module M, erasing its namespace  and starting over


6B.6.1 Forms of importation

The standard way to import a module, like CheckBook, is by
import CheckBook
so that the functions (and variables) in the module can be referenced by mentioning the module's name, e.g., CheckBook.doDeposit(100, "April 1").

There is another form of importation, which allows you to reference the variables and functions in a module directly, without stating the module's name first. The format is

from MODULE import NAME1, NAME2, ..., NAMEi
For example,
from BankAccout import withdraw 
This imports module CheckBook but makes only withdrawvisible to the program. You can now say
cash = withdraw(1000)
and this executes withdraw in module BankAccount. But the name, BankAccount, is not visible, so if you try
print BankAccount.getBalance()
or even BankAccount.withdraw(1000), you will see this message:
NameError: name 'BankAccount' is not defined

A variation of the above import statement is

from MODULE import *
Such as from BankAccout import *. This makes all the definitions in BankAccount visible to the program, so that you can say
from BankAccountimport *
  ...
cash = withdraw(1000)
print cash, getBalance()
But again, the name BankAccount is not visible.

Finally, we can use this pattern of commands to import functions for interactive testing:

import BankAccount
... BankAccount.withdraw(...) ...
This lets us test the function, withdraw.

After we test the function and repair it, we can retest it without restarting the Python interpreter by stating,

reload(BankAccount)
... BankAccount.withdraw(...) ...
This reloaded the module with its repaired coding of withdraw.


6B.6.2 The search path

When you write modules, where do you place them? When one module imports another, where does the Python interpreter look to find it?

The first place the Python interpreter looks when importing a module is in the same folder where execution was started. But if the module is not found there, the interpreter follows a preset search path to other folders.

To see the search path, start the Python interpreter and type the following:

$python
>>> import sys
>>> sys.path
You will see a list of folder names, something like this:
['', 'C:\\IBMTOOLS\\utils\\logger', 'C:\\Documents and Settings\\YourName',
'C:\\Python22\\DLLs', 'C:\\Python22\\lib', 'C:\\Python22\\lib\\lib-tk',
'C:\\Python22', 'C:\\Python22\\lib\\site-packages']
>>>
The folders are searched in this order for modules to import. Some of these folders hold modules for doing graphics and animations; in others, you can place Python modules that you would like to reuse.

Also, once you have started Python, you can append a new path to the end of sys.path like this:

sys.path.append("C:\\Documents and Settings\\YourName\\MyPythonModules")
Or, you can define a PYTHONPATH environment variable, like you can reset Window's PATH variable. (See the Python documentation for information about how to do this.)


6B.7 Design: Software architectures

Buildings and cars have architectures, and so do complex programs. A program's software architecture is its pattern of module assembly. The box-and-arrows diagrams seen in this chapter are simple depictions of software architecture. Although it is too soon for us to make intensive study of software architecture, we can benefit from a survey of several forms of software architecture that have proved useful in practice.

In the examples that follow, we use diagrams to display an architecture. The boxes represent modules, a solid line denotes the importation of one module by another, and a dotted line denotes the transfer of information from one module to another, typically as answers returned by function calls.

Sequential architecture

The oldest form of module assembly is a sequence, where each module represents a stage of computation that accepts input, runs to completion, and produces an output. This is a sequential architecture and looks like this:

The modules in the diagram are usually program modules. A sequential architecture is used to divide a too-large program into manageable, clearly defined stages. Many traditional business-processing programs (e.g., accounting/payroll) are designed in this format. In contrast, programs that require repeated human interaction cannot be easily constructed in this style.

Model-view-controller architecture

Most single-user, interactive computer programs are designed to match the model-view-controller (MVC) architecture:

As described in the examples in this chapter, each module has clear-cut role: the model module contains the data structure that represents a physical object like a gameboard or spreadsheet, the controller contains the algorithm that solves the desired problem by interacting with the model, and the view accepts user input and displays information from the model, as directed by the controller.

The MVC-architecture is particularly useful for building programs that require graphical user interfaces (GUIs), because the complex coding for the GUI is kept separate from the algorithm and data structure that are needed to solve the problem. As a result, a developer can improve any one of the GUI, algorithm, or data structure without having to rewrite the entire program.

Layered (tiered) architecture

Programs that must interact directly with machines and hardware (so called systems programs), such as operating systems and networking programs, are often organized in a layered or tiered architecture. Modules are organized in layers, like the layers of an onion:

Each layer represents a degree of closeness to the hardware: A layered architecture is designed for portability, that is, the ability to move easily from one hardware configuration to another. For example, if your computer uses an Intel chip, its hardware configuration is different than a computer that uses an AT&T chip. If you have a layered architecture that operates on the Intel chip, you can move the entire system to the AT&T hardware by rewriting only the kernel modules so that their functions compute with the AT&T chip. This is how modern operating systems and networking software is made to operate on differing machines.

Hierarchical architecture

A hierarchical architecture is a collection of modules organized as a ''tree,'' that is, the functions in each module are called by at most only one other module (its ``parent module''):

The hierarchical architecture is especially useful for safety-critical software (aircraft controllers, medical-device controllers) where program correctness is paramount. This is because the hierarchy makes it easy to design the large system top down (please review the section on top-down programming in the previous chapter) and then test and prove correctness bottom up.

Bottom-up correctness checking means that we begin testing and proving correctness of those modules that do not call functions in other modules. If needed, we can argue correctness with the additional knowledge of who the parent module must be. After the ``leaf'' modules are proved correct, we move to proving the correctness of the parent modules that use functions in the already proved modules. In this manner, we systematically test and prove correctness in careful, well defined stages.

There is a strong connection between this form of bottom-up correctness validation and inductive reasoning in mathematics.

Database/blackboard architecture

When a system accumulates huge amounts of knowledge, the knowledge can be pooled in a central data structure, called a database. A database architecture is built around such a database that is shared by many other modules, called processes:

The database acts as a central library of knowledge that all the processes can exploit. A standard example is a bank's database of customer accounts, which is shared by the bank's workers and ATMs.

The database architecture can also be used to design a form of problem solving where a family of ``experts,'' each of whom have a special skill, can work together to solve a complex problem. The problem is inserted into the database module, which is now called a blackboard, and the experts study the problem and each makes small steps towards its solution, leaving the results the results of the small steps in the blackboard module. Eventually, with the cooperation of all the experts, the problem is solved.

Here is a trivial example: one expert knows how to add integers, another knows how to subtract them, and a third knows how to multiply. The blackboard holds the problem,

(3 + 2) * ((5 -1) * 4)
The experts take turns contributing their solution steps until the problem is completely solved, e.g.,
(3 + 2) * ((5 - 1) * 4)
=> (3 + 2) * (4 * 4),   by the subtraction expert     
=> (3 + 2) * 16,   by the multiplication expert     
=> 5 * 16,   by the addition expert
=> 80,   by the multiplication expert
Blackboard architectures are commonly used for language processing and machine learning.

Client-server architecture

One disadvantage of the database archtecture is that there is only on database module, which can slow the performance of the entire system if there are many processes all demanding information from it.

A client-server architecture is built to allow multiple processes, called ``clients,'' to request information or services from a ``server,'' where there are multiple servers available to service such requests:


The client does not care which server handles its request, and there is a mediating module, called an object request broker (ORB) or software bus, that connects a client to an appropriate (e.g., least busy) server.

The client-server architecture adapts well to systems that are distributed on many computers, and it has been highly successful for network applications, such as Google, where thousands of web-browser clients contact Google and are routed by its ORB to one of its hundreds of servers.

The structure of the ORB is crucial to the success of the architecture, and CORBA (Common Object Request Broker Architecture) is a formal description of the functions that must be written for the ORB. Any company that wishes to build a ``standard'' client-server architecture writes its ORB to match the CORBA description. Having said that, we note that Microsoft has its own variant of client-server architecture called COM (now part of .NET), and there is a third popular competitor called Enterprise JavaBeans.

Peer-to-peer architecture

One flaw of a client-server architecture is that the ORB is highly used; if the ORB is slow or fails or must be redesigned, the entire system is dramatically affected. A less centralized variant of a client-server architecture is a peer-to-peer architecture, where each module in the system acts as a client as well as a server as well as a mini-ORB:

Each module knows some of its neighbor modules. If a module has a request (it acts as a client), it sends the request to its neighbors. When a neighbor module receives a request, its mini-ORB asks the module to service it. If the module itself cannot service the request, the mini-ORB forwards the request to the module's neighbors. In this way, a request travels through the system until it reaches a module that can service it and return a result.

Peer-to-peer networks are highly reliable in the sense that they operate even when modules appear, disappear, and fail. They are unreliable in that there is no guarantee that any particular request will be serviced promptly or at all. For this reason, the architecture is often used for distributed-library activities (e.g., sharing copies of bootlegged music files).

Summary

Professional software developers spend decades of their careers becoming software architects, and a software architect often specializes in just one or two architectural forms. A good software engineer knows how to judge if a particular architecture is appropriate for solving a complex problem, and a talented software architect can design the entire system, just like a talented team of skyscraper architects can design blueprints and details for a new skyscraper.

All these developments are made possible by the invention of the module --- a program unit that holds a data structure, some start-up commands, and some functions that are contacted by other modules.


6B.8 Semantics of modules: Multiple global namespaces

When a module is imported, its namespace is constructed, its code is executed, and all its definitions (variables and functions) are saved in the namespace, which lives permanently. It is critical that we understand this, so we will trace in detail an example that uses two modules, a Clock and a Con(troller):
FIGURE=======================================================

"""Clock models a clock that ticks and emits an alert every 60 ticks

   Operations:  tick (increments the clock by one tick),
                bigTick (increments the clock by  how_many  times)
                getTime (returns the current time)
"""
time = 0                    # Line 6

def tick() :                #      8
  global time               #      9
  time = time + 1           #     10
  if (time % 60) == 0 :     #     11
      print "Alert:", time  #     12

def bigTick(how_many) :     #     14
  for i in range(how_many) :#     15
    tick()                  #     16

def getTime() :             #     18
  global time               #     19
  return time               #     20

ENDFIGURE=========================================================
FIGURE======================================================

"""Con tests the Clock module"""

import Clock               # Line 22

x = [ Clock.getTime() ]    #      24
Clock.bigTick(2)           #      25
x.append(Clock.time)       #      26

ENDFIGURE=========================================================
When the Con module is started, computer storage looks like this; there is an empty namespace for Con:

The i.c. variable remembers the next command to execute and the n.s. variables remembers which namespace to use with the command to execute.

The import Clock instruction constructs a namespace for Clock and starts its commands --- i.c. is set to 4 and n.s. is set to Clock:


Variable time is constructed in Clock's namespace, and the functions at Lines 6, 11, and, 15 are saved in Clock's namespace. At this point, importation is complete, and execution returns to Line 24 in Con:
  
Notice that Clock's namespace remains and that an entry for Clock appears in Con's namespace.

The assignment at Line 24 proceeds in the usual three steps: First, Con's namespace is checked to see if x exists; it doesn't, so a cell is constructed. Next, the expression, Clock.getTime() must be calculated to its value. First, Clock is found in the namespace: the name refers to the Clock's namespace, so the latter is searched for function getTime. The function is found and is started at Line 19 with its own private namespace:


The n.s. variable is reset, and within the namespace for getTime, the global n.s. is correctly set to Clock (and not Con).

Line 19 makes time link to the variable held in the function's global namespace. We see that return time calculates that time computes to 0 (which is the value of time in namespace Clock, and the 0 is returned to the called of getTime. This lets us build a list to hold the 0 and finish the assignment to x at Line 24:


Line 25 calls Clock.bigTick, which immediately calls tick:

Function tick executes, the conditional in Lines 9 and 10 is executed (the condition evaluates to False), and the function finishes. Then, tick is called a second time, and a fresh copy of its private namespace is built again. The function computes a second time and finishes. Line 26 computes the value of Clock.time and appends it to list x.


The example shows that each module has its own, permanent namespace, and when we call a function, the function's ``global'' namespace is the one associated with the module in which the function is defined.

Finally, the example showed that it is possible to reference the variables within a module by merely mentioning their names, e.g., Clock.time. But

never assign directly to a variable defined in another module!
That is, we should never try to do Clock.time = Clock.time + 1 Remember that a key reason for breaking a program into modules is to group together variables with the functions that maintain them. No harm is done by looking at a variable in another module, but changing the variable means that the ``maintenance'' is no longer done in just one module!


6B.9 Summary

A module is a file that holds a data structure, some start-up commands, and functions that can be called by other modules.

A module that has no functions that are called by other modules is a program module or program, for short.

One module links to another by importing it. The command forms are

When testing a module you have imported, here are some new Python expressions you can use:
dir()  # lists the bindings in the current namespace

dir(M) # lists the bindings in the namespace of module, M

print M.__doc__  # displays module  M's  documentation string