AustinTek homepage | Linux Virtual Server Links | AZ_PROJ map server |

Teaching Computer Programming to High School students: An introductory course using Python as the high level language; variables, conditionals, iteration, modules, structured programming

Joseph Mack

jmack (at) wm7d (dot) net
AustinTek home page

v20090511, released under GPL-v3.


Table of Contents

1. External Coding Resources (getting help)
2. First Python Program(s)
3. Editor: writing and saving programs
3.1. Available Editors
3.2. Saving Files: where to put them
4. Executing a program
4.1. Executing Python in unix/Mac/cygwin
4.2. Executing Python in Windows
5. Variables
6. Conditional Evaluation
6.1. Basic Theory
6.2. Temperature Controller, air conditioner only
6.3. Temperature Controller, air conditioner v2
6.4. Temperature Controller, air conditioner and heater
6.5. Temperature Controller, air conditioner, heater and window opener
6.6. Temperature Controller, formatting output
6.7. Review: Programming Practices
6.8. Review: Conditionals
7. Iteration
7.1. Iteration Basics
7.2. simple for loop
7.3. another simple for loop
7.4. iterating over a range
7.5. the fencepost error
7.6. while loop
7.7. Review: Iteration
8. Subroutines, procedures, functions and definitions
8.1. Function example - greeting.py: no functions, greeting.py
8.2. Function example - greeting_2.py: one function, no parameters
8.3. Problems with python indenting
8.4. Separation of function declaration and function definition: order of calls to functions
8.5. Function example - greeting_3.py: one function, one parameter
8.6. Scope - greeting_4.py
8.7. Code execution: Global and function namespace
8.8. Function example - volume_sphere(): function returns a result
8.9. Checking Code
8.10. Using Math Libraries
8.11. Function Documentation
8.12. Return Value
8.13. Function properties
8.14. Review: Functions
9. Modules
9.1. making a module of volume_sphere(): writing the function to go into a module
9.2. making a module of volume_sphere(): handling the global namespace code
9.3. Code Maintenance
9.4. Functions: recap
9.5. Function example: volume_hexagonal_prism()
9.6. making a module of volume_hexagonal_prism()
9.7. Do the tests give the right answers?
9.8. Code clean up
9.9. Train Wreck code
9.10. Review: Modules
10. Giving a Seminar
10.1. Explaining the function of code: Separate "what" from "how"
10.2. The Seminar Environment
10.3. Speaking
11. Structured Programming
12. Recap

Abstract

Class lessons for a group of 7th graders with no previous exposure to programming (as of Aug 2008, now 8th graders - in the US 7th, 8th grade are called Middle School). The student are doing this after school on their own time and not for credit. My son's school didn't want me to teach the class using any of the school facilities, as they thought it would compete with a class in Java given to advance placement math 12th graders. However I could use the school facilities, if I didn't teach anything which would fullfil a requirement (which I assume meant anything practical). So the class is at my home and is free. Since this is a hobby activity for the kids, I don't ask them to do homework. As they get further into the class and take on projects, I'll be quite happy for them to work on the projects in their own time, but will let them decide whether/when to do this.

Note

The notes here are being written ahead of the classes. I've marked boundaries of delivered classes with "End Lesson X". Each class is about an 90mins, which is about as much as the students and I can take. After a class I revise the material I presented to reflect what I had to say to get the points across, which isn't always what I had in the notes that the students saw. Material below on classes that I've given will be an updated version of what I presented. Material below on classes I haven't given, are not student tested and may be less comprehensible.

The big surprise to me is that when you're presenting new material, the students all say "yeah, yeah we got that" and want you to go on. However when you ask them to do a problem, they haven't a clue what to do. So most of my revisions to the early material were to add worked problems. I also spend a bit of time at the start of the class asking the students to do problems from the previous week's class.

I've found that when asking the students to write a piece of code that some of them (even after almost a year) can't turn a specification into code. Instead I have to say "initialise these variables, use a loop to do X, in the loop calculate Y and print out these values, at the end print out the result". Usually these steps will be given in a list in the notes here. Once I'd shown the students how to initialise and write loops, I had expected them to be able to parse a problem into these steps without my help. However some of them can't and I changed my teaching to reflect this.

The kids bring laptops do to the exercises and they display this page by dhcp'ing and surfing to my router where this page is also stored.

Students are using Mac OS, Windows XP and Linux. For WinXP, the initial lessons used notepad and the windows python. For material starting at the Babylonian square root, I changed the WinXP student over to Cygwin (still using notepad).

In later sections I started having the kids do a presentation on what they'd learned. The primary purpose of this was to accustom the kids to talking in front of groups of people. They also had to organise the material, revealing how much they'd remembered and understood it. The ??? covered a body of material large enough that it took 2 classes and homework to assemble the presentation. For the next section of work, I'll have them do presentations on smaller sections of the material, and then have them present on the whole section at the end. I've hidden most of the answers to questions in footnotes (so they won't see the answers easily during class). However later, when the students are scanning the text to construct their presentations, that it's hard to find what they're looking for. I don't know what to do about that.

Material/images from this webpage may be used, as long as credit is given to the author, and the url of this webpage is included as a reference.

1. External Coding Resources (getting help)

Few people remember all the syntax and the range of instructions in any language, even if they code in it year-in and year-out. Everyone codes with books, manuals and the internet handy. You shouldn't go out of your way to learn the exact syntax for any particular instruction; through repetition you'll come to know them as a matter of course. Thinking about the problem and figuring the best way to code it will take most of the time in writing a program. Because no-one can remember syntax, most computer exams are open book: instructors know that it takes longer to look up an answer in a book than to retreive it directly from memory. A book isn't much help in a computer exam anyhow, but it will save you when you can't remember syntax.

You do have to know what sort of instructions might be available in any programming language, so you can say "Now how do you test if (x=0)?".

Note
End Lesson 7

2. First Python Program(s)

The traditional first program in C is to print the string "hello world!". Here it is in immediate mode in python.

>>> print "hello world!"
hello world!
>>> 

up-arrow with the cursor and edit the line (using the backspace key) to have python say "hello yourname" (e.g. "hello Joe").

Programmers don't hardcode names and numbers into programs. We want the same program to serve anyone and any set of numbers. Instead we use variables (which hold variable content) to hold any value/string that could change. Using any combination of recalling old lines, editing and adding new instructions that you can figure out, execute these two lines in order.

>>> name = "Joe"	#note "Joe" is a string. Note 2: comments in python start with '#'
>>> print "hello " + name
hello Joe
>>> 

Let print some numbers.

>>> age = 12
>>> print "hello, my age is " + age
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: cannot concatenate 'str' and 'int' objects
>>> 

What happened here? print only knows about strings. In the above code, age is an integer, not a string. Let's make age a string.

>>> age = "12"
>>> print "hello, my age is " + age
hello, my age is 12
>>> 

You can't always rely on a number being available as a string. Maybe it was calculated from your birthdate and today's date, in which case it will be a number. You give print the representation (as a string) of the number.

>>> age = 12
>>> print "hello, my age is " + repr(age)
hello, my age is 12
>>> print "hello, my age is ", age 
hello, my age is  12

3. Editor: writing and saving programs

You've been running programs in immediate (interactive) mode. After any changes, to see the changed output, you must arrange for all lines to be run again in order. Real programs consist of many lines and possibly many files. These are saved to be run again later. Any changes, bug fixes, improvements can be made, while leaving the bulk of the file unchanged.

To write these files you need an editor. An editor displays on the screen all the characters that will be saved to the file. There are no hidden and undisplayed characters for formatting and printing that a part and parcel of word processors. An editor shows you exactly what will be saved, no more, no less. The editor saves the file with the name you give it and doesn't try to be smart and give your file an extension that it thinks is better.

An editor must do the following

  • enter and delete text
  • move/copy lines or blocks of text
  • search for and replace (translate) text
  • navigating

    • tell you the line number and column at the cursor.
    • move to a given line number or search string. when you get there, display that line in the middle of the screen.

It's not a lot to ask.

3.1. Available Editors

Note
If you've been using an editor to do your binary math examples above (rather than pencil and paper) and you're happy with it, then you can skip to saving_files.
  • unix/cygwin:

    The most commonly used editors are vi and emacs: unfortunately the human interface for both is execrable. I miss MultiEdit, the editor from my DOS days. Some people use pico, but the license is not GPL compatible, so a pico-like editor nano has be written. Both nano and pico are also available for windows.

    • vi

      This is the simple editor, with a minimum number of commands required to edit. It's simplicity was required in the early days of networked computers when the small bandwidth available only allowed simple tools to be used to administer remote machines.

      There are many "improvements" to vi, all of which are directed to destroying the best feature of vi - its simplicity, while ignoring the real problem, the human interface. The "improved" versions of vi color each word differently (anyone for yellow letters on white background, how about dark blue on grey) and attempts to render html as it would be displayed in a browser, not as you'd want an editor do display it (so you can edit it).

      vi was written for keyboads which didn't have enough keys to issue commands and to edit at the same time. This is no longer true for modern keyboards, there are plenty of keys now, but in vi you have to remember whether you're in edit of command mode. As well vi gratuitously beeps at you all the time. The only purpose of this is to keep others in the house awake while you're coding at 3am.

      The documentation for vi is impenetrable and doesn't tell you how to turn off the improvements, only how to make them more complex.

      To counter the "improvements" to vi, I use a version from about 1990, before the era of "improvements".

    • emacs

      This is the all singing/all dancing editor which does everything that a computer can do. It's likely that someone has already mathematically proven that emacs will do anything that any computer will ever be able to do. If ever your computer can make coffee, you'll first be able to do it from emacs. Unfortunately my early apprenticeship in emacs was stopped dead when I had to use a DEC windows machine for a couple of years. One of the vital keys for emacs, C-s, was a signal to the VAX to turn off the keyboard (neat feature huh?). A couple of years following that of administering machines over a phone line (probably 4800bd) and I was a vi convert.

      Networks and computers are a lot faster now, and it's reasonable to use emacs over a network. However most commands require two lots of keystrokes, when one would do.

      There have been no attempts to "improve" emacs. Everyone who uses it, likes it just as it is.

    • nano

      nano download

From the NCSA discussion mailing list (and other places) comes these suggestions for windows/Mac editors other than vi and emacs

  • Mac:

    • textedit
    • textwrangler (http://www.barebones.com/products/textwrangler/)
    • textmate (http://macromates.com/)
    • subethaedit (http://www.codingmonkeys.de/subethaedit/) (shareware)
  • windows:

    • IDLE which comes with python. (IDLE seems to be a regular programming editor and will save files with any extension.) IDLE gives you a standard immediate mode python prompt (but without history recall, i.e. the up-arrow doesn't recall the last command - if you want history, then you need to use the python interpreter). You can do a file save from here: it will save the sign-on info and messing around you do. This is not what you want. Instead do file-new window, when you'll get a regular editing window.

      Warning
      IDLE (at least initially) wants to save your program in a directory that it has no business saving files to. See the note for notepad++.

      Getting Started with Python in IDLE (http://www.ai.uga.edu/mc/idle/index.html)

    • notepad, comes with windows
    • notepad++ (http://notepad-plus.sourceforge.net/uk/site.htm) (the download link is hard to see, it's orange on white).

      Warning
      notepad++ by default wants to save your files in c:\Program Files\Notepad++. do not do this. The Program Files directory and its subdirectories are for well tested and safe programs to be used by everyone on the computer. These directories are not for your development files, which must be kept where they can't do any damage, like in a subdirectory of \Documents and Settings\UserName or \My Documents.
    • crimson edit (http://www.crimsoneditor.com/). I tried this when I was forced to use windows and didn't like it, but I can't remember why - I think it was too complicated to use.
    • editpadlite (http://download.jgsoft.com/editpad/SetupEditPadLite.exe) - the download link is hard to find - it's at the bottom of the page, below the exhortations to buy the professional version. The installed version wants to talk to you a lot. You'll have to find out how to turn it off if you want to do any work. It's free for non-commercial use. It supports unlimited file size and multiple files open at the same time, which notepad does not. (I don't have enough brains to handle two files open at a time.)
    • windows extensions for Python (http://sourceforge.net/projects/pywin32/, also possibly known as win32all) which I know nothing about, but which is supposed to have an IDE.
    • pico available for windows and unix. (have not tried it, apparently you need to run it with pico -w to stop it linewrapping - pico was originally used to compose e-mail).
    • nano

      nano download, look in the NT directory.

When I had to code on windows (worst 3 days of my life), I installed cygwin and could pretend I was working on a unix machine. This is a reasonable approach if you already know the unix tools.

For this class pick any editor you like. Be prepared to try a few editors before finding one you like. Everyone is different and everyone likes a different editor. I like mine really simple. It seems that others like theirs complicated.

If you find yourself programming on a long term basis in a unix environment, and you expect you'll be sitting in front of a machine that someone else has setup (e.g. at work), that you should learn one of vi or emacs. It's quite disappointing to realise that the program you spend most of your time with, the editor, has such a bad interface and that no-one has designed an editor for unix like the much nicer ones available on the much dispised Microsoft platforms.

3.2. Saving Files: where to put them

pick a name for your python files directory e.g. class_files.

  • unix/cygwin: in /home/username/class_files/
  • windows: in \Documents and Settings\UserName\class_files\ or \My Documents\class_files\ (two names for the same place).

Your files need to be in a place where no-one else is likely to run them accidentally. Until your files are well tested and of general use, they should be kept in your directories.

4. Executing a program

Create your work directory e.g. class_files and cd to it. Fire up your editor and save this text as hello_world.py

print "hello world!"

4.1. Executing Python in unix/Mac/cygwin

Make the file executable.

chmod 755 hello_world.py

At the command prompt, run it

# python hello_world.py
hello world!

Congratulations, you are now officially a computer programmer.

The above command works because python will have been installed in your $PATH.

You can invoke the interpreter from within your program using the shebang convention. Here's the new code (the "#!" is called a shebang)

#! /usr/bin/python 
print "hello world!"

You run it from the command line like this

# ./hello_world.py
hello world!

4.2. Executing Python in Windows

In windows you'll be in a command box at \Documents and Settings\UserName\class_files (or \My Documents\class_files which is the same thing). The python install sets up the registry so that you can directly execute the python program. You only need to do

hello_world.py

Congratulations, you are now officially a computer programmer.

You can execute the program by clicking on the filename using windows explorer, but the output window will open and close too fast for you to see what happened.

The unix execution options are still available, but you don't need them in windows

  • Direct execution

    #python not in the PATH
    "\Program Files"\Python25\python hello_world.py
    

    To add python to the PATH see How to set the PATH in windows (http://www.computerhope.com/issues/ch000549.htm). After doing this you can type python rather than "\Program Files"\Python25\python

    #python in the PATH
    python hello_world.py
    
  • The shebang convention

    • hello_world.py for python not in the PATH

      #! c:"\Program Files"\Python25\python
      print "hello world!"
      
    • hello_world.py for python in the PATH

      #! python
      print "hello world!"
      

    executing the program

    hello_world.py
    

5. Variables

In a program, data is held in variables. variables can be manipulated by functions appropriate for their data type (i.e. math on numbers, string functions on strings). Fire up your editor in your class_files directory and try this (you do not need the shebang if you're in windows). Save the file as variables.py (you can swipe the code with the mouse if you like. In X-window, the tabs are replaced by spaces, which may cause problems with your python interpreter. You could edit the mouse swiped code to replace all occurrences of 8 spaces with a tab.)

#! /usr/bin/python
""" variable.py
    Kirby. 2008
    class exercise in variables
"""
#
#put in a name (your own if you like).
name="Kirby"    	#name is a string, it needs quotes.

#Put in an age.
age=12          	#age is an integer, however it will be output as a string.

age_next_year=age + 1   #another integer

print "Hi, my name is " + name + ", a word with " + repr(len(name)) + " letters."
print "I'm " + repr(age) + " years old."
print "On my next birthday I will be " + repr(age_next_year) + " years old"
print "and will have been alive for " + repr(int(round(age_next_year * 365.25))) + " days."
# variable.py -------------------------

If it didn't run

  • fix any syntax errors (mistakes in code that python doesn't understand)
  • if there are no syntax errors and you're in a unix environment, did you make the file executable.

What's the code doing?

  • Start with documentation (needed for all code) so in 6 months you (and other people) will know why you wrote it.

    Comments

    • multiline blocks start and end with """. These blocks are picked up by python utilities which scan code and assemble documentation for a project of many files. These comments describe what the code does (not how).
    • single line comments start with '#'.

    Put the filename somewhere and a description of what the code does (not how it does it - that belongs in the code). If you're ever going to give this code to anyone else, you need to put the author and date in the code.

  • in the next 3 lines, some variables are declared (you tell python that the variables exist), and in the same line these variables are given values.
  • In the print lines, the variables were output, along with some text for the user.

    • repr() turns a number into a string
    • Due to years not being an integer number of days long, the function round() drops the 0.25,0.5 or 0.75 at the end of the number of days you've been alive. This will give you a number ending in ".0". To drop the ".0", you turn the number into an integer with the function int().
  • The comment at the end (variable.py) lets the reader know they're at the end of the program. In the old days, when code was transmitted by unreliable phone lines, you would sometimes get code that just finished, and you couldn't tell if this was supposed to be the end of the code, or the phone line had dropped during transmission. Put something at the end of the code (even "end of code") to let the reader know that they really did get all the code.

Rerun the program without round() or int() to see what happens. When you modify working code, you comment (#) out the current code (keeping it incase you decided to return to it - always keep working code, till you're sure that you have better code), make a copy and then modify the copy. Here's how you'd start modifying the last line of the code above.

#print "and will have been alive for " + repr(int(round(age_next_year * 365.25))) + " days."
print "and will have been alive for " + repr(round(age_next_year * 365.25)) + " days."

When you have the code you want, you delete all the commented attempts.

Change the line

print "On my next birthday I will be " + repr(age_next_year) + " years old"

to output your age on your next birthday, by using the variable "age" and some arithmetic, rather than the variable "age_next_year" [1] .

change this line again (including the text) to give your age in 2050 [2] .

Note
Did you get a reasonable answer? Starting with your age now, count your age to the same day at the end of the decade (say 2010), then add 40. The 2008 in the above line must be the year of your last birthday. If it's 2008 and your last birthday was in 2007, then you will need to put 2007 in the above line.

6. Conditional Evaluation

6.1. Basic Theory

All computer languages allow comparison of the contents of variables with the contents of other variables (or with numbers, strings...). Tests are

  • < less than

    <= equal or less than

    Note
    In the spirit of completely gratuitous changes, python doesn't accept =< used in other languages, but does accept <= (which is reasonable, it's how you say it verbally). Just watch for the syntax error messages (you can't let your life be dominated by other people's idiocyncracies). It would be reasonable in a new language like python, to allow both <= and =<.
  • > greater than

    >= greater than or equal

  • == equal

    != not equal

Following the test, code execution can branch in various ways e.g. (here shown in pseudo code)

  • if (temperature > 80 degrees)
    then
    	turn on airconditioner
    endif
    

    There is one branch of the conditional, which turns on the airconditioner. If the temperature is less than 80, then the program continues execution without turning on the airconditioner.

    What does the code immediately above do at 79 [3] ?

  • if (temperature > 80 degrees)
    then
    	turn on airconditioner
    else if (temperature =< 60 degrees)
    	turn on the heater
    endif
    

    There are two branches of the conditional. If the temperature is more than 80 or less than or equal to 60, then the program branches, otherwise execution continues without branching.

    What does the code immediately above do at 60 [4] ?

  • if (temperature > 80 degrees)
    then
    	turn on airconditioner
    else if (temperature < 60 degrees)
    	turn on the heater
    else
    	open the windows
    endif
    

    The conditional has three branches and does something different for temperatures below 60, 60-80 and greater than 80.

    What does the code immediately above do at 80 [5] ?

Note: python doesn't have this problem:

Here is a common coding bug. The comparison operators are unique to comparisons, but the "==" and "=" operators are similar, at least visually.

x=0 means "assign x the value 0"

x==0 means "test if x has the value 0"

In a conditional you want

if (x==0)
	do something
else
	do something else

If instead you do

if (x=0)
	do something
else
	do something else

then x will be assigned the value 0. The language must know whether a command succeeded (allowing execution to continue, or to bail out with an error) and since assigning x=0 will always succeed, then execution will branch to the "do something" branch, independant of the original value of x.

Since you're unlikely to ever want to execute the instruction

if (x=0)

the language should trap this construct as a syntax error. None of them do, at least till python came along. This is why rockets continue to blow up, cars have different bumper heights and we had lead in paint for so long.

Here's the official Python Tutorial, section 4.1 If Statements (http://docs.python.org/tut/node6.html#SECTION006100000000000000000)

Note
I live in one of the few (the only?) country that uses Fahrenheit, miles, lbs... (and the country's currency is loosing value, while the economy is only surviving by exporting jobs to our competitors.) If you live in the rest of the world, please substitute other values in these examples. I also live in a part of the world where you need an airconditioner in the summer and heater in winter (I lived quite happily in another part of the world for the first 30yrs of my life without these things and had some trouble adjusting to living indoors). I still wonder why people settled in such places when they didn't have to.

6.2. Temperature Controller, air conditioner only

Here's the python syntax for a single branching conditional statement.

if x < 0:
	print 'Negative number found'

fire up your editor to code up the file temperature_controller.py

  • declare a variable temp giving it a value of 85.
  • if temp>80, print a message saying "the temperature is" and giving the temperature followed by a period. Then on the same line, output "Am turning on the air conditioner.". Use blanks and spacing to make the output human readable.
  • save your program and run it. Once it runs, change the value of temp to 75 and rerun the program, to check that it runs correctly.

Here's my code [6] .

6.3. Temperature Controller, air conditioner v2

Here's python code for a two branch conditional.

if x < 0:
       print "Negative number found"
    else:
       print "non negative number found"

starting with your current temperature_controller.py, add code to output the string "no action taken" if the temperature is 80 or below. Check your code with values of temp above and below 80.

Here's my code [7] .

6.4. Temperature Controller, air conditioner and heater

Here's another python conditional construct

if x < 0:
      print 'Negative number found'
elif x == 0:
      print 'Zero'
else:
      print 'Positive number found'

Use this construct to add a branch to temperature_controller.py which turns on the heater at 60 or below and outputs a message describing its action.

Here's my code [8] .

6.5. Temperature Controller, air conditioner, heater and window opener

Change the preceding code to open the windows if the temperature is 61-80. Test your code to confirm that all branches execute at the expected temperatures. Here's my code. [9] .

6.6. Temperature Controller, formatting output

In our current version of temperature_controller.py, the string "the temperature is " is in all branches, so will be output no matter what. In this case the string could be output once, below the declaration of temp. Here's a print statement using a trailing ',' that doesn't put a carriage return at the end of the output line.

name = "Homer"
print "my name is", name,

Use this code fragment to only have one copy of the string "the temperature is" in the code. Here's my version [10] .

Note that you can't get the period correctly spaced after the temperature. Python thinks it knows what you want better than you do and outputs a gratuituous blank that you didn't ask it to output.

Formatted output allows you more control over your print statements. Use this code fragment to produce a better output for temperature_controller.py (the %d says to put the decimal value of the argument in that place in the string).

>>> temp = 65
>>> print "the temperature is %d." % temp
the temperature is 65.

Here's the improved code [11] . Note that python still outputs a gratuituous blank when it executes the comma.

Note
End Lesson 8

6.7. Review: Programming Practices

Note
The material reviewed here is delivered in dribs and drabs throughout the course. Don't expect to know it all if this is your first pass throught the material.

The most expensive part of writing a program that is in production for a long time, it not the original writing of the code, but maintaining it (fixing bugs, adding new features) which is done by people who don't spend a lot of time on the code and rarely have any idea what the whole program does. If you're the original coder or the maintainer changing the code, which of these following practices should you use, to make it easy for other people to work on your code.

  • at the top, write the name of the file, give author information (name, contact info), date the code was written, license, and short description of the purpose of the file.
  • initialise all variables at the top. use descriptive names for variables. In a comment, put all useful info about the variable (e.g. expected limits. loop counters can have single letter names (usually i,j,k) unless a descriptive name is obvious.
  • label and delimit sections of code e.g.initialisation, main() and functions. Put the name of the file in the last line of the code.
    #!/usr/bin/python
    # my_code.py
    
    # Joseph Mack (C) 2009, jmack@wm7d.net
    # License GPL v3
    
    # purpose:.....
    
    #---------------------------
    def function_1()
    	.
    	.
    	return
    
    # function_1----------------
    
    # initialise variables
    foo = 3
    
    #---------------------------
    #main()
    
    # my_code.py----------------
    
  • document the purpose of each piece of code e.g. the purpose of a loop, what a set of conditionals is controlling. If input is expected from the user, describe the limits of the input, or what is expected.
  • put self contained blocks into modules.

answer: [12]

Since debugging code is twice as hard as writing it, what will happen if you write the smartest code you can write?

answer: [13] .

6.8. Review: Conditionals

Write code (call it cruise_control.py) to run the cruise control of your car (cruise control maintains the speed of your car on the freeway, without the driver needing to use the accelerator).

  • initialise the speed of the car (anything will do for the first run, say 20mph)
  • The cruise control has an upper set speed (which the car will not exceed) and a lower set speed (which the car will not go below - there is no antonym for "exceed" it seems). Initialise the upper set speed to 65 mph, the lower set speed to 60mph.
  • Write code that does the following

    • outputs the speed to the screen
    • If speed < lower set speed, print the message "accelerating" to the screen, increase the car's speed to the lower set speed and output the new speed to the screen.
    • If speed > upper set speed, print the message "braking" to the screen, decrease the car's speed to the upper set speed and output the new speed to the screen.
      Note
      A real cruise control doesn't have braking. Air resistance is sufficient to slow the car down if it's running too fast. However for a coding exercise, we'll have braking.
    • If lower set speed<=speed<=upper set speed, do not change the speed, and print "maintaining speed" and the speed to the screen.
      Note
      a range (band) of input that results in no change of output is called hystersis (http://en.wikipedia.org/wiki/Hysteresis). A physical example of hystersis is backlash (http://en.wikipedia.org/wiki/Backlash_(engineering)) where a gear or knob, on reversing direction of movement, has a small range where is doesn't engage the other parts of the system.
      A car travelling on even a flat highway is sometimes ascending small gradients and slowing down, and then sometimes descending small gradients and speeding up. Wind makes continuing small changes in the car's speed. If the car tries to maintain exact speed, the cruise control will be alternating between braking and accelerating. This will be jarring to the car's occupants. It's better to let the speed vary in a small range where the cruise control will not act. This range is normally quite small (certainly less than the 5mph we've assumed here).

  • allow the program to exit
  • How many different initial speeds will you need to test this code? Show that it works for all ranges of input speed (print out test results).

answer: [14]

7. Iteration

7.1. Iteration Basics

Computers are ideally suited to run the same piece of code over thousands, if not millions of pieces of data. Doing the same thing over and over again is called iteration and all computer languages have instructions for iteration. The part of the code that is iterated is called a loop. The philosophy behind python's iterative commands is different to all other languages.

  • python

    list = .....
    for every item in list:
    	do something
    
  • other languages

    for item from start_number to end_number, advance a step of size
    	do something
    

In python if you create the list with range(), you have to create the whole list before you start the loop. In the 2nd method, you create one item at a time. In python, if your list is larger than can be held by the memory in your computer, then your program won't run. We'll find an example of this later when calculating π. There we'll use xrange() which generates one item at a time, rather than a list.

7.2. simple for loop

Here's example python code which iterates over a list of numbers (the '[' and ']' delineate a list).

#!/usr/bin/python

for x in [0,1,2,3,4,5]:
	print x,

Code this up as interation_1.py and check that you get reasonable output. What happens if you leave off the ',' at the end of the print statement [15] ?

The body of this loop has one instruction (the print() statement). The length of the loop isn't so obvious: no-one is pedantic about this - including the required blank line at the end, you could say that the loop is 3 lines long, or maybe 2 (the for statement and the print() statement) or maybe 1 (the print() statement).

Note

If you run this code at the python prompt (>>>), you will need a blank line after the print() statement, to let python know that the indentation has changed back to the previous level of nesting (here the left most column).

Compare this piece of code

#!/usr/bin/python

for x in [0,1,2,3,4,5]:
	print x,
	print "hello"

with this piece of code (the only change is the indentation for the print "hello" statement). Predict the output of the two pieces of code and then run the code.

#!/usr/bin/python

for x in [0,1,2,3,4,5]:
	print x,

print "hello"

Change the code in iteration_1.py to

  • initialise a variable sum to 0 before you enter the loop (note you should use descriptive variable names like sum rather than just y).
  • add x to sum each time you go through the loop, still outputting the number x across the screen in each iteration.
  • after exiting the loop, output the value of sum on new line, with some helpful text like "the sum of the numbers is".

Here's my code [16] .

Warning

x (the variable whose value is being set by the for statement) is called the control or loop variable. It's only purpose is as input to the loop statements. The loop variable should be treated as a readonly variable. (i.e. you shouldn't try to change it) i.e. do not put the control variable on the left hand side of any instructions in the loop.

DO NOT DO ANY OF THESE

x = x + 2
x = 2
x = "hello"

Changing the loop variable is unsafe programming. If you're debugging a long loop, you don't want code in the middle messing with the control variable. In some languages (fortran) you aren't allowed to change the loop variable, or it will change in unpredictable ways. (In python, in this loop above, changing x wouldn't cause any great problems, other than being unsafe programming.)

If you want to do something with the control variable, make a copy of it (with say y = x) and do whatever you need on the copy y.

7.3. another simple for loop

Using iteration_1.py as a template, write iteration_2.py to output and sum the squares of the numbers 0..5. Here's my first version [17] . Notice that the calculation of x*x is done twice. You shouldn't ever do a calculation twice - it takes too much time. If you need the result in two places, you do the calculation once and save the result in a variable. Here's a better version [18] .

Note

some new syntax: these are identical

y = y + 3
y += 3

other operators which use the same syntax are 
-=, *=, /=, %=

Modify iteration_2.py to use this new syntax. Here's my new version [19] .

7.4. iterating over a range

Most often, you aren't iterating over an irregular or random list of numbers. You usually iterate over a well behaved and ordered list. Rather than enter a list by hand, risking a mistake, python (and a few other languages) have functions to generate lists. Python's command is range() and true to python form, it behaves differently to the equivalent command in other languages.

Here's the way everyone else does it

range(start, end, [step]) #[] indicates an optional parameter, default value here is 1

range (0,10)
 0,1,2,3,4,5,6,7,8,9,10

Here's python's version

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

The argument (parameter) 10, says to generate the default list (i.e. starts at 0, stepping/spacing of 1) and stop at the element before 10 (simple huh?). In this case range() produces 10 numbers, as you might expect, but see below for further developments.

For fun, try a few other sets of parameters for range().

Copy iteration_2.py to iteration_3.py and use range() to generate the list of numbers from 0..5. Here's my code [20] .

Well that was confusing, if you want to generate a list from 0..5, you need an argument of 6 for range().

Here's some more examples using range().

#as above, starting at 0
>>> range(0,10)		
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
#starting at 1, ending element before 10
>>> range(1,10)		
[1, 2, 3, 4, 5, 6, 7, 8, 9]
#starting at 1, in steps of 3, ending at element before 10.
>>> range(1,10,3)	
[1, 4, 7]

Using the previous code as a template, sum the squares of all the numbers from 0..100. Then sum the squares of all the numbers from 0..100 that are divisible by 7. Here's my code [21] .

Note
End Lesson 9

7.5. the fencepost error

fencepost error (http://www.eps.mcgill.ca/jargon/jargon.html#fencepost%20error)

We've run into the fencepost error before. when I asked you to march 10h places down the alphabet from 'A'. The correct answer was 'Q', but someone got 'P'.

You want to build a fence 100ft long, in sections of 10'. How many sections of fence will you be building [22] and how many fenceposts do you need [23] ?

Same question again: if I want to sum the squares of the n consecutive integers, how many iterations (of the loop) do I need [24] ? If I want to sum the squares of integers 0..n, and how many iterations do I need [25] ?

This will fool you over and over and over, from the start of your programming career to the end. Anytime you iterate (go) from a start boundary condition, over a range, to an end boundary condition, you'll have to do a test count, to determine whether you're counting fenceposts or fences. (When you're programming, it's not always obvious whether a variable is a fence or a fencepost.) If you've got it wrong and you only have one iteration to do, the computer will think you only have to do 0 iterations. Your loop won't be executed and you'll think that there was something wrong inside the loop, that it didn't add (or whatever) your number.

7.6. while loop

First Steps to Programming (http://docs.python.org/tut/node5.html#SECTION005200000000000000000) shows a while loop.

A while loop has this structure

determine condition
while condition is true
	do a
	do b
	.
	.
	determine condition again

#code executed when condition becomes false

The while and for loops can be logically equivalent. These two produce the same output:

  • while loop

    variable = 0
    while (variable < 10):
    	variable = variable + 1
    	print variable
    
    
  • for loop

    for x in range(10)
    	print variable
    
    

The while loop requires a little more setting up, but unlike the for loop which is good for regular input, the while loop can be setup to handle just about anything (here pseudo code accepts a name)

print "enter your name followed by a carriage return"
name = ""
char = getkeystroke()	#I made this function up
# != is not equal - the loop is entered for all chars except a carriage return
# if the char is a carriage return, which instruction is executed next?
while (char != carriage return):
	char = getkeystroke()
	name += char

print name

The essential features of the while loop are

  • the while loop waits for some condition or event (above is entry of a carriage return), whose arrival is unpredictable, before exiting (or looping). When the condition or event doesn't (or does) happen, the loop exits (or loops again).
  • Some information or state accumulates during the execution of the while loop, (here the contents of the variable name) which is available on exit. This information/state/variable must be initialised before entering the while loop.

7.7. Review: Iteration

  • The two types of loops are while and for (also known as do) loops. while loops can handle any iterative situation, but are more complicated to setup. for loops are simpler to setup, but are only usable under what (useful and common) situation?
  • The use of the more complicated while loops is then is normally reserved for what situation?
  • write code (sum_numbers.py) that uses a loop to sum the numbers between two user settable limits (make the limits 1, 10). When the program exits, print to the screen
    The sum of the numbers from lower_limit to upper_limit is sum
    
    (substituting actual values for lower_limit, upper_limit, sum.)
  • copy sum_numbers.py to sum_numbers_2.py. Add another piece of code to sum the same numbers using a while loop (and output the same string as above).
  • Copy cruise_control.py to cruise_control_2.py
    • change the accelerating/braking code to inc/decrement the speed, by an an amount called adjust whose value is 1mph.
    • when the car is maintaining speed, change the speed by a random amount, whose value ranges from variability_lower_limit to variability_upper_limit (set these to -2 and +2 respectively). This simulates the effects of small hills and wind on the car (a reader of your code will not know the function of adding a random number; make sure it's documented).
      Note
      You will need the random module. Look up (with google) a python function that returns a random int whose limits are (a,b). Change the car's speed with this function.
      Output the new speed to the screen.
    • The code exits after one pass. We want the code to loop forever, checking the speed of the car.
      • add a variable cruise_control_on and set its value to 1.
        Note

        python does not have the boolean constants true,false or functions to operate on them (neither do several other commonly used languages). For languages without boolean values, the convention is that 0==false and !0==true boolean (http://en.wikipedia.org/wiki/Boolean_datatype). (i.e. 1, -1, 255, are all true). (You can use any convention you like, and your code will run just fine. However no-one will be able to read it.)

        If you have boolean constants you can do this

        variable = TRUE		#obvious meaning
        while (variable):
        	do something
        

        In python you have to do this instead

        variable = "on"		#meaning clear but clunky code
        while (variable == "on"):
        	do something
        

        or this

        variable = 1		#relies on you realising that 1 is true, dangerous code.
        while (variable):
        	do something
        

        To be safe you'd have to do this

        TRUE = 1
        variable = TRUE		#obvious, but you shouldn't have to define TRUE
        while (variable):
        	do something
        

      • use a loop control construct to test the value of cruise_control_on and then either send the execution to the cruise control code or else exit.
        Note
        since we won't be changing the value of cruise_control_on the loop will never exit. You will have to exit the program with ^C.
    • This loop runs too fast to see what's happening on the screen. The cruise control doesn't need to check the car's speed every microsecond. The car's computer will likely be doing other things as well (in a big loop) and will only be checking the car's speed occasionally. To simulate other activities of the car's computer, add a line in the loop which puts the code to sleep for 1 sec, each time through the loop. You will need the time module. Use google to find the function in the time module that puts the code to sleep for 1 sec.
    • change the print statements so that the speed values line up vertically down the screen as the program runs.
    • The code needs to check whether the cruise control is on/off. Put this commented line at a place in your code, where it would look to see if the value of cruise_control_on has changed.
      #cruise_control_on = check_cruise_control_status()
      
      This commented line does nothing, but it's the place where a call to look for updates would happen. (There are problems doing including this line at this stage in the code. Look at the answer after thinking about where this line of code might go.)

answers: [26]

8. Subroutines, procedures, functions and definitions

Code is not written in a single continuous long file. Code is broken up into blocks of length of about 5 lines to 2 pages (longer blocks are too big to keep in your head). Depending on the language, these blocks of code are called functions, procedures or subroutines. Python calls them functions. Functions can be kept in their own file or with other files. Functions in one file can be called by import'ing them from another file.

Note

when I learned computing they were called "subroutines". The language C later differentiated two types of subroutines; procedures and functions and I never adopted the nomenclature (only a pendant would notice the difference - a couple of strokes of the keyboard and you convert one to the other). Python uses the word "function".

I work in what was once called a supercomputer center (when it was fashionable to use such a term). The term used there is subroutine. You can use whatever term you like. Python people will expect the term "function".

8.1. Function example - greeting.py: no functions, greeting.py

Here's a program that has no functions. It gives a greeting. Call it greeting.py

#! /usr/bin/python
""" greeting.py
    greeting without any functions
"""

#---------
#main()

print "hello user"

# greeting.py ---------------------------------

The code includes documentation and white space to separate the logical sections (in this case, the documentation and the code). The dividing line and "#main" are to let people know where the code starts executing. This is optional, but some delineator is needed to help you (and others) read your code later. Remember if you're being paid to write code, or work in a group, others will not be impressed if they have to struggle to read it.

Modify this code to say "hello, this code was written by (your name)" [27]

8.2. Function example - greeting_2.py: one function, no parameters

If we want to call the greeting from several places in the code, we turn the printing of the greeting into a function. Call this file greeting_2.py

#! /usr/bin/python
""" greeting_2.py 
    greeting as a function
"""

#------
#functions

def print_greeting():
	"""prints greeting
	"""

	print "hello user"

# print_greeting---------

#main()

#call the function
print_greeting()

# greeting_2.py ---------------------------------

What's going on:

  • a function is declared by the tuple

    def function_name (parameter_list):

    Here the parameter list is empty.

    Note
    tuple: from the word "multiple". Any grouping of one or more factors, variables, parameters that are constant in number in a particular situation. The tuple for a function declaration is the 3 strings above. The tuple for the contents of a PB&J sandwich is "peanut butter, jelly". A PB&J sandwich never has a tuple of 1 or a tuple of 3. In English a tuple of 2 is a double. You can get through computing without ever having to use the word "tuple" (I don't use it) but others use it, so you have to know what it means.
  • indenting:

    the definition of the function (the lines of code that does the work) is indented. See the next section for problems with python indenting.

  • the name of function is added in a comment at the end of the function at the indent level of the string def. This lets you know where the function ends. If you're looking at the tail end of a long function, you will forget which function you're working on. This is optional (as far as the language is concerned), but will save you grief later when scanning your code.
  • The function is called from main() with a list of parameters that match the definition of the function (here empty).
    Note
    nomenclature: main()calls the function. When the function has finished executing, it returns (execution returns to the line following the call).
  • Use white space and empty lines to make your code readable.
  • The comments "#functions" and "#main()" are to help the reader.

8.3. Problems with python indenting

Indenting is a common coding practice to show the reader logical separation of code from the surrounding code. In all languages, except for python, the indenting is only for the reader and is ignored by the compiler/interpreter. In other languages, pairs of "{}" (braces, squigglies) are used to delineate blocks of code; pieces of code that have their own scope (variables created in a block don't exist after the block exits).

Python uses the indenting to show both the start/end of functions (and blocks), and for the reader to show logical sections of code. Proper indenting is required for the code to run, and is not just for clarity to the reader. The Style Guide for Python Code (http://www.python.org/dev/peps/pep-0008/) requires 4 spaces for python indenting. For compatibility with old code, python will accept a tab or 8 spaces (but not mixtures of a tab in one line and 4 spaces in the next).

The problem with python indenting is that python has syntax errors which are silent to inspection by eye (you can have mixtures of tabs and spaces which are the same on consecutive lines, but all you'll get from python is "syntax error" on the first non-white space character). If code has an error in it, you should be able to see it. (This sort of feature is called "broken by design".) This is a rocket waiting to blow up.

There are many style of indenting for readability and code nesting with curly braces Indent style (http://en.wikipedia.org/wiki/Indent_style). Each style has its adherants, and without any tests as to the best style, or any interest in running such tests, claims of superiority of any method are specious and cast doubts on the credibility of the claimant. There is no doubt that people can best read the style they're used to (just like they can best use the keyboard layout they're used to). People working in a team will usually be required to use the style that the leader is most familiar with.

It turns out that (without knowing it) I adopted the Allman style for curly braces, using a tab (1 key stroke) for indenting. I will be using a tab for indenting in the code here. You can use whatever your python interpreter will accept. Be aware that a tab when mouse swiped in X, will appear as 8 spaces in the target code (after a mouse swipe, I have my editor replace all occurrences of 8 spaces with a tab).

To help you read the code, you adopt an indenting style so that the indenting level indicates the block level. The compiler doesn't care if the indents and blocks don't match, the compiler looks at the block level. If your indenting and blocks don't match, you'll may have logical control problems (the program will do something, but it may not do what you think it should do). Some editors match pairs of "{}" so that you can check your blocks. In python, indenting is blocking. Editors don't show matching indents and until you're used to the python indenting, you'll run into apparently intractable problems. Here's an example:

#!/usr/bin/python

output_string = ""
sum = 0
for i in range(0,10):
	output_string += str(i)
	for j in range(0,i):
	        sum += j*j

	#lots of lines of code

	        #including multiple levels of endenting

	                #like these lines





	 print output_string

Here's the output

# ./indent.py
  File "./indent.py", line 18
    print output_string
	              ^
IndentationError: unindent does not match any outer indentation level

The problem is that the line "print output_string" is a tab followed by a space (2 indents) instead of a single tab.

Despite the advice of the python people, I think it's safer to use tabs for code indenting

  • it's one keystroke (it's simpler)
  • you can search for them with your editor, and replace any error causing tab-space combinations with a tab.

If your code is nested to great depth (not a great idea), deeply nested code will be diplaced to the right across the page.

8.4. Separation of function declaration and function definition: order of calls to functions

In python, function declaration (the tuple of def name (parameters):) and definition (the lines of code that do the work) occur together. Functions often call other functions. If this second function has not yet been declared, the language/interpreter/compiler doesn't know what to do with the name.

Compare these two programs.

#functions declared in order
def fn1:

def fn2:
	call fn1 #fn1 already declared, no problems

#main()
	fn2
#---------

#functions declared out of order
def fn1:	
	call fn2 #don't know fn2, will have to look for it

def fn2:

#main()
	fn1
#---------

All languages have mechanisms for handling calls to functions that aren't known. You can list all your functions in any order you like (alphabetical by name, the order you wrote them, grouped by functionality), at the top of the file (the usual place) or at the bottom of the file (some people do this).

In many languages, particularly those designed to write code with thousands of files in a large application, the code writer is required to declare functions in a separate header file, which is read before the code file is read, so that all functions are declared before any code is looked at. Separating declaration from definition allows the compiler/interpreter to process files singly. This helps when you're working on one file in a large project.

Interpreted languages (like python, perl) keep track of the function calls they don't know about and exhaustively search all files in their pervue - it will search the Module Search Path. (http://docs.python.org/tut/node8.html#SECTION008110000000000000000) - in the hopes of finding the declaration. This can take a while, but what the heck, it makes life easier for you (let the computer work hard - that's it's job - it's not your job to make life easier for the interpreter, it's the interpreter's job to make life easy for you).

However for small projects, worrying about this sort of thing is a hill of beans. You're probably looking at the screen for 90% of the time trying to figure something out and you don't care if the computer is scanning files for an extra few seconds. Some people have all their functions at the bottom of the file, so the interpreter collects many calls to unknown functions before it gets to the function declarations below. These people are just as productive as the people who have their function declarations in order at the top of their files.

8.5. Function example - greeting_3.py: one function, one parameter

Usually a function needs information from the calling code. The information is passed as parameter(s).

Here we're also going to get input from the user. (Getting data directly from the user is not easy. It's simpler to get it from a file. Here we're going to get data from the user.)

Note

Let's say we want to get the user's name and print it on the screen. Getting input from users is fraught with problems and makes life difficult for the programmer. See Conversing with the user (http://www.freenetpages.co.uk/hp/alan.gauld/tutinput.htm) and Saving Users From Themselves (http://linuxgazette.net/issue83/evans.html).

  • they'll enter "y" or "n" or their phone number.
  • they can enter a string which when printed to your screen will erase your hard disk, so you have to check that the the string they enter is safe (this is a lot of work).
  • It's a waste of a computer to have it talk to one person. You should enter the person's name into a file with thousands of other names and let the computer process the data for everyone.

Since you're doing this for the class and it's your own computer, let's assume you're a benign user who can reply with their name when asked for it.

Try this as greeting_3.py

#! /usr/bin/python 
""" greeting_3 
    greeting as a function
"""

#------
#functions

def print_greeting(user_name):
	"""prints greeting
	"""
	print "hello %s, nice to meet you" % user_name

# print_greeting---------

#main()

#get input
resp = raw_input ("What is your name? ")

#call the function
print_greeting(resp)

# greeting_3.py ---------------------------------

In main() the parameter passed is resp (a string). In print_greeting(), the parameter arrives as user_name. What's going on? In main() the instruction print_greeting(resp) pushes the value of resp onto the parameter stack; the instruction doesn't push the name resp onto the parameter stack. When the function print_greeting() runs, the command print_greeting(user_name) says "assign the value of the first parameter to the variable user_name". At this stage the function doesn't know whether the parameter is a string, int or real (the data in the parameter can only be reasonably treated as a string). You have to tell the function that the parameter is a string, which you do in the first instruction that uses user_name (the print statement).

In greeting_3.py

  • The name for the parameter in the calling code (resp) has some relevance to the surrounding code.
  • the name for the parameter in the calling code (resp) bears no relation to the name of the parameter (user_name) in the function.
  • The name for the parameter in the function is usually some generic name that will work for any situation that the function could be called upon to work in.
  • The two names could be identical, they could be quite different.
  • user_name is not known by the calling code (or any other functions)
  • resp is visible to print_greeting

8.6. Scope - greeting_4.py

Scope is the range in which a variable can be referenced. In programming languages,

  • all variables declared in main() will be visible in any functions that are called by main()
  • the variables declared inside a function will not be visible to other functions or to main().

Let's look at this modified version of greeting_3.py. Call it greeting_4.py. It has extra print statements (to debug the code).

Note
Since adding extra print statements to code fills the screen with output, it's helpful to prepend the name of the routine/function to each output so you can decipher the extra output. When you're done debugging the code, you will first comment out and then later delete these extra lines.
#! /usr/bin/python 
""" greeting_4 
    greeting as a function
"""
#-----------------
#functions

def print_greeting(user_name):
	"""prints greeting
	"""
	print "hello %s, nice to meet you" % user_name
	print "print_greeting: the value of resp is " + resp

# print_greeting--------------

#main()

#get input
resp = raw_input ("What is your name? ")
#call the function
print_greeting(resp)
print "main: the value of user_name is " + user_name

# greeting_4.py ---------------------------------

Variables are only known/visible to functions at levels above in the calling hierachy. Variables are not visible to functions at the same level in the calling hierachy. (The calling level of functions is also described as nesting. Outside the function, variables are known only to nested layers above.)

  • user_name is declared in print_greeting(). It is only visible in print_greeting()
  • resp is declared in main(). It is visible in both main() and in print_greeting()

Scope allows people to write functions independantly of each other knowing that only main() will be able to see the contents of their function. A variable user_name could be used in several functions, and each user_name could have different values.

Because of scope

  • the only connection between a function and the calling code is the function's name, the parameters that are passed to it and the value(s) that it returns.
  • any variable names and values within the routine/function are not visible to any other code.
  • the routine/function will see variables declared by calling code.

While you don't have to know about variable names used by other function, you do have to know names in the code at levels above (which call your routine/function).

In greeting_4() there are two variables

  • resp: Where is it declared, where is it visible: In main(), in print_greeting()[28] ?
  • user_name: Where is it declared, where is it visible: In main(), in print_greeting()[29] ?

The program has instructions to output the value of both variables in each of the two functions main() and print_greeting(). Predict the output of the running the program. Then run the code and explain the output from the two extra print statements [30] .

Note
End Lesson 10

8.7. Code execution: Global and function namespace

The namespace of the code that starts execution is called "global namespace". When you pass a file to the python interpreter, python starts executing on the first instruction it finds in global namespace, i.e. the first code that isn't a function. In other languages, execution starts at a function called main(), which has the top level (global) name space and which calls all other functions. For these lessons, in each file which has function(s), I've put a comment #main() in the place where the python interpreter starts executing. In code thus far, both python and other languages would start executing at the same place. However in a later section on making a module, we will see how python's assumption, about where to start executing, gets us into trouble.

Variables are declared in a namespace: if you declare a variable user_name in a function print_greeting(), then user_name exists in the print_greeting() namespace.

Any function can change the values of a global variable (including functions that haven't been written yet). If none of your functions change any of the global variables, you can't guarantee that someone else, at some later time, won't write one that does. Or you could blunder and write one that changes a global variable without realising it.

Note
In some other languages, any function can read and write global variables; in others you declare a global variable to be read-only by declaring it to be const (constant). In python, global variables are read-only. If you want to write to a global variable (i.e. change it), you have to explicitely declare that you're going to do so inside the function. This "are you sure?" step, requires you to think twice about doing it, but doesn't stop you from doing it. (Thanks to Rick Zantow for his explanation at global variables http://www.velocityreviews.com/forums/t354870-unboundlocalerror-local-variable-colorindex-referenced.html)

Global variables are regarded as sign of poor (or unsafe) programming style. If you use a writeable global variable, expect someone to ask you to justify doing so and to justify not using some safer programming practice. In later demonstration code, you will see global variables being used: usually the safer programming practice would add complexity, obscuring the point of the demonstration code. If you're programming rocket guidance systems, air traffic control or heart monitors, you won't be using any global variables.

8.8. Function example - volume_sphere(): function returns a result

As well as requiring data from the calling routine (passed as parameters), functions are often written to generate a result that's returned to the calling routine.

Call this file volume_sphere.py.

#! /usr/bin/python
""" volume_sphere.py
    returns volume of sphere
"""

#---------------
#functions

def volume_sphere(r):
	pi=3.14159
	result=(4/3)*pi*r*r*r
	return result

# volume_sphere---------------

#main()

#get input
resp = raw_input ("What is the radius of your sphere? ")

#call the function
volume=volume_sphere(resp)	#assign the result of the function call to volume

#formatted output
print "the volume of your sphere is %f" % volume
#this gives the same output
#print "the volume of your sphere is " + repr(volume)

# volume_sphere.py ---------------------

Let's walk through this line by line

#get input
resp = raw_input ("What is the radius of your sphere? ")

You'll be prompted for a number, which you'll enter on the keyboard. Your entry will be assigned to resp

#call the function
volume=volume_sphere(resp)	#assign the result of the function call to volume

When there's several things to do on a line, the parser starts from the right hand side. The parser has a value for resp so next looks at volume_sphere() which, because of the (), is recognised as a call to a function. The interpreter finds volume_sphere and passes resp to volume_sphere(). Note: the interpreter doesn't look at volume=, at least yet. Execution now passes to volume_sphere()

	pi=3.14159

the variable π is assigned the value 3.14159.

	result=(4/3)*pi*r*r*r

the value of (4/3 pi*r3) is assigned to result. Why didn't we just splice the value of π into the formula for the volume of the sphere?

  • It makes the code more readable. Everyone recognises "(4/3)*pi*r3", but you won't neccessarily recognise a formula with lots of numbers in it.
  • Having all the constants at the top of the routine helps the reader know what the routine needs to know.
  • Having constants at the top of the routine, allows you to change them without having your editor traipsing through tested code, where by some clumsy editing, you might unintentionally change something else, and not realise it. Very few people rerun their tests when they make simple changes like this. Even if you do, your tests may not be exhaustive enough to pick up an editing disaster. Don't tempt disaster by re-editing working code when you don't intend to exhaustively retest it.

(We'll shortly replace the line pi=3.14159; stay tuned.)

Here's the new part for this lesson.

	return result

The function pushes result onto the stack and exits. (You can only return one item. If you want to return two numbers, you could return one list containing two numbers.) Execution returns to main() where the interpreter now sees

	volume=result

volume is assigned the value of result

Run the program. Did you get a run-time error about non-ints? An error at run-time means that the code syntax was correct and the program started to run, but you asked the program to do something it can't do. Writing code is full of situations like this; it's a coder's life.. You have to figure your way out of them all the time.

The error message you get here is misleading. The problem is with resp; is it a number? If not, what is it [31] ? You sent the volume_sphere() a string and asked it to use a string as a number, which it can't do, so the program crashed.

The problem with resp is fixed by turning the string contained in resp into a real (floating point) number. All languages have ways of interconverting numbers and strings and they all look much the same. Here's how it's done in python.

number=float(string)
Note

Strong Typing and Weak Typing.

Strongly typed languages will not allow you to send incompatible data types to a function. They check data types before the code is ever allowed to run. Strongly typed languages require the declaration of the function to include the type of any result and the type of each parameter. The above code in a strongly typed language would fail at compile/interpret time and you'd get an error message about mismatched parameter types ("resp" is a string and can't be passed to "volume_sphere" which requires a real). The development time for a strongly typed language is longer, but there will be less chance of errors at run time.

Python is a weakly typed language. Weak typing allows you to quickly write short pieces of code without the relatively large overhead of writing header files and explicitely typing (declaring) parameters. For small programs in a strongly typed language like C or C++, the overhead of writing header files can be daunting,

Weak typing also allows you to write sloppy code, which passes syntax checking, but which crashes at run-time. So you don't use python for rocket guidance, air traffic control or heart monitors. You do use it for short programs, for programs that will only be run a few times and for programs where errors will not be catastrophic and where someone will be on hand to fix them when they do cause a crash.

Both strongly typed languages with a long development time and low error rate, and weakly typed languages with a short development time and high error rate have their place. A bank chooses a bullet proof armoured wagon to transport its money and you choose a paper bag to transport your groceries.

  • Demonstrate the function float() at the python prompt. Feed it a fews strings (strings are delimited by quotes) representing numbers e.g. "3.14159", "98.4". What do you think float() will do if you feed it a number (e.g. 3.14159) (no quotes) [32]

  • Where in your program should you turn the string into a quote? You could turn "resp" into a number in the calling code, or you could turn "r" into a number in the function. Show how you would do both. Think about which is the best solution and justify your choice [33] .

You can fix the code however you want. Here's my fixed code.

#! /usr/bin/python
""" volume_sphere.py
    returns volume of sphere
"""

#---------------
#functions

def volume_sphere(r):
	r=float(r)      #allow function to accept both strings and numbers
	pi=3.14159
	result=(4/3)*pi*r*r*r
	return result

# volume_sphere---------------

#main()

#get input
resp = raw_input ("What is the radius of your sphere? ")
#resp=float(resp)       #don't turn resp from a string into a float here

#call the function, passing a string
volume=volume_sphere(resp)

#formatted output
print "the volume of your sphere is %f" % volume

# volume_sphere.py ---------------------

Run this program with a radius of 1 (which gives me the result 3.141590). Now you have to check that your result is OK. Try with bc.

echo "(4/3)*3.14159" | bc
3.14159

It's the same answer as the python program. Does the answer look OK? If you're not sure, do it by hand (give π a value of 3 just to get a rough estimate) [34] .

So what went wrong? In case you've forgotten: (4/3) is an integer operation which gives a result of 1. The volume of a sphere of radius 1 is 4.188 (and not 3.14159 as found by the program). To invoke real number operations with bc, you need the math libraries, invoked with bc -l. To stop yourself falling into a trap, always invoke bc with the -l option.

echo "(4/3)*3.14159" | bc -l
4.18878666666666666665
echo "(4.0/3.0)*3.14159" | bc -l
4.18878666666666666665

Here's the fix

	result=(4.0/3.0)*pi*r*r*r

8.9. Checking Code

  • You must check/test every piece of code you ever write; not the whole code as one piece, but check the code line-by-line. (Print out every variable on the way through; then comment those lines when they prove OK; then later remove these commented lines).
  • Include at least one check by hand, using simple numbers that you can do in your head. Don't rely on computers to check computers: all code uses the same libraries and makes the same assumptions. A badly written piece of code could give the same result in many different computer languages (4/3 is 1 in all computer languages).

    When the Space Shuttle flies, it has 4 identical computers running, checking all parameters. If one of the computers gets a different result, the assumption is that there's a hardware failure in that computer and it is shutdown. The 2nd assumption is that the code is perfect and there never will be a software failure. What if the same bug is running in all 4 computers? What you really want is 4 independant (not identical) computers: each computer having different hardware designed and built by separate teams, running a different OS, running software written by 4 independant teams in different languages. Everyone knows the problems with accepting this 2nd assumption, but no-one is prepared to pay for the cost of 4 independant computers.

    There are many contenders for the worst software failures in history. Here's Simpson Garfkinkels top ten History's Worst Software Bugs (http://www.wired.com/software/coolapps/news/2005/11/69355).

    My favourite is a rocket that blew up because a line of code had a ',' rather than a '.'. This line of code was never exercised in tests. During flight, that line of code ran and the program crashed, causing the destruction of the rocket.

8.10. Using Math Libraries

It's not a good idea to type in the value of π you could make a mistake. Any language that does math, has the value for π (and other useful numbers) built in. You don't need to remember exactly how to include π. Instead look up the documentation or look in google for a piece of working code (google for "python math pi") - I found CGNS Python modules -- Introduction Python" (http://cgns-python.berlios.de/Ipython.html).

There's a couple of places you can put the import statement.

  • at the top of the file, before the function definitions
  • in the function after the documentation
  • at the beginning of main()

All of these will work. However functions are designed to be easy to move/copy to other code. What will happen to your function if you put the import math statement at the beginning of the file or at the beginning of main() and you copy the function to another python file, by swiping it with your mouse [35] ?

Modify your code to use the value of π from the python math module.

8.11. Function Documentation

Here's what good documentation for a function looks like. If ever you hope to code with other people, or to remember in 6 months what your code does, you will need to do something like this for every function/piece of code you write.

#! /usr/bin/python
""" volume_sphere.py
    returns volume of sphere
"""

#---------------
#functions

def volume_sphere(r):
	"""name      - volume_sphere(r)
	   version   - 1.0 Feb 2008
	   method    - volume =(4.0/3.0)*pi*radius^3
	   parameter - radius as string, integer or real, valid all +/-ve numbers
	   returns   - volume of sphere, float
	   Author    - Homer Simpson, Homer@simpson.com (C) 2008
	   License   - GPL v3.
	"""

	import math     #import the whole math module (including lots of stuff you don't need)
	r=float(r)      #convert string input to float.
	                #this allows the function to accept either a string or a number

	                #pi in module math is called math.pi
	result=(4.0/3.0)*math.pi*r*r*r
	return result

# volume_sphere---------------

#main()

#get input
resp = raw_input ("What is the radius of your sphere? ")

#call the function, passing a string
volume=volume_sphere(resp)

#formatted output
print "the volume of your sphere is %f" % volume

# volume_sphere.py ---------------------

Add appropriate documentation to your version of this function showing

  • name of routine (parameter(s))
  • description of parameter(s) and valid input types and ranges
  • description of result (if there is one)
  • author, contact information, date, copyright (if you want to claim it, optional but good idea)
  • license (optional, but good idea)

    The license dictates the terms under which other people can use the code. The Gnu Public License (in my opinion) is the best license for releasing code: You retain copyright, anyone else can use you code for anything they like. If they in turn release code based on your code, they must also release the source code (including your code, or the modified version of your code). The GPL is designed so that any improvements to freely released code is also freely released.

8.12. Return Value

It's easy to disguise where a function returns. Functions can have many conditional statements, any of which (depending on the flow of the program) can give the return value. Code is allowed to return from anywhere in the function, but doing this makes it hard to read. Badly written code looks like this.

def my_function (param1, param2...)
	if (some complicated condition)
		.
		.
		return (some complicated expression)
	elif (some complicated condition)
		.
		.
		return (some complicated expression)
	elif (some complicated condition)
		.
		.
		return (some complicated expression)
	else
		return (some complicated expression)
	endif

It can be hard to debug this sort of code: it's hard to find all the return statements. Instead there must (should) only be one return statement; the last statement in the function. All output should be put in some obviously named variable (like "result") which will be the argument to the return statement. The code you should write is this

def my_function (param1, param2...)
	if (some complicated condition)
		.
		.
		result = (some complicated expression)
	elif (some complicated condition)
		.
		.
		result = (some complicated expression)
	elif (some complicated condition)
		.
		.
		result = (some complicated expression)
	else
		result = (some complicated expression)
	endif

	return result

This is still hard to read, but you've done your best (multiple conditional statements aren't easy to read). If you're looking for the return values, you can find the string "result" with your editor,

8.13. Function properties

procedure/subroutine/functions have the following properties

  • they're a logical block that can be described in a few words (e.g. gets input data, checks data, sorts data, output results)
  • it's a piece of code that's called twice (or more) from other code (never duplicate a block of code, always make a function out of it).
  • it's well tested and can be put away in a separate block and the internals of how it works can be forgotten about.
  • the interface with the calling code is well defined e.g.volume_sphere.py accepts a string (or number) and returns a number.
  • the implementation (the code that does the work) can be rewritten without any change in function/behaviour/output as seen by the calling program. e.g. there are many types of sorting routines. If your subroutine does a sort, then should be able to replace the code with a different sort routine without making any changes to the calling code.
Note
End Lesson 11

8.14. Review: Functions

Copy cruise_control_2.py to cruise_control_3.py.

  • Change the code, which does the braking, accelerating and maintaining speed, into functions. Call these functions int braking(int), int accelerating(int) and int maintaining_speed(int). These functions take the current speed as a parameter and return the new speed. These functions are so short and simple that they don't need any extra documentation.
  • Make the conditional block in main() into a function int check_speed(int), which takes the argument speed and returns speed.
  • The sleep() function currently is in main(). It doesn't belong there. The sleep() function is a stub standing in place for code, which is checking the brakes, temperature... Create housekeeping() to take the place of the extra code and put the line of sleep() code in it. Have housekeeping() output the string "housekeeping". Should you output this string before or after sleep()? To help you think about it, remember that any code can hang (bugs, hardware problems). If the code called from housekeeping() hung, what do you want to see on your screen? Think about it for a day or so before looking at the answer [36]
  • The loop controlled by cruise_control_on is actually a loop running the whole time the car is running. Whether the computer checks the cruise control depends on what buttons you've pressed on the steering wheel. Change the while loop to run forever, and use the variable cruise_control_on in a conditional statement.
  • Make check_cruise_control_status() which returns cruise_control_on, and uncomment the proxy line in main() that calls it. Put some comment in the function so that the reader will know what it's doing. This function could turn on a light on the dashboard to indicate that cruise control is on. Add this dummy line to the function.
    #write code here to activate light indicating that cruise control is on.
    
    Fix up the code so that cruise_control_on can be toggled on/off and the cruise control will be activated/deactivated. Check by changing the code so that the function returns 0 (what do you see in the output now?). Where should cruise_control_on be initialised? It's not clear to me. It could be in the initialisation section of the code, or it could be initialised in check_cruise_control_on(). Presumably when you wrote the code that set check_cruise_control_on it would be clearer.

answer: [37]

9. Modules

9.1. making a module of volume_sphere(): writing the function to go into a module

Let's say your function is one or both of

  • useful enough that you (or others) will want to use it and you are going to put it in some publically accessable place (your local network or post it to the internet).
  • part of a project and you're finished with it and don't want to think about it anymore and would like to put it in a place where python can pick it up at run time.

You make a module out of your code. From there, you can import the function into your other python code.

In its most basic form, a python module is just a file containing a function; making a module only a matter of copying the function into a file in a directory in PYTHONPATH (which includes your current directory). However you can do a much nicer job than that.

Before you unleash this code on the unsuspecting world: is it safe? Once people start copying it to their own machine, you've lost control of it and you can't fix it if it's broken. At that stage, you'll have to endure e-mails from irate users telling you that your function is crashing their machine, or their rockets are blowing up.

  • Consider all possible inputs: what do you want the function to do if the radius is -ve? You can't return 0; 0 is a valid volume for a sphere of radius 0. You could return an error condition, but we haven't done error handling in python yet. We could return a +ve volume, but that will cause problems for the calling routine, if for some mathematical reason we don't know about, a -ve radius is valid.

    Is it reasonable to return a -ve volume? The formula is valid for -ve radii: who are we to say that a -ve volume is invalid? If we take this point of view, what might be the consequences to the calling code of passing a -ve radius to volume_sphere.py? How would the calling routine get a -ve radius in the first place? A distance can be -ve, but the radius is the distance from the center to the surface: it's always +ve. We could take the point of view that if a -ve radius is an error, then the calling routine will trap such conditions and not call volume_sphere.py.

    These things need to be thought about and made explicit in the documentation. I think the cleanest thing to do in the the case of a -ve radius is to let the calling routine handle it. If the calling routing finds that the radius is -ve, and that this is an error condition, then it shouldn't ask for the volume of a sphere with -ve radius. As the function writer, we can't second guess the validity of a -ve radius; it may be valid.

  • Your function doesn't have to do everything. What it does, you must document; what it doesn't do, you must document. After that, it's up to the user not to get into trouble with it.

Before we let the code go, we want to to exercise all the features of volume_sphere.py (it can accept string, reals, +ve/-ve). Add these lines at the bottom of the code and rerun it to check your file.

#make a few calls to volume_sphere() to test it
print "the volume of a sphere of radius  1 is " + repr(volume_sphere(1))
print "the volume of a sphere of radius -1 is " + repr(volume_sphere("-1"))
print "the volume of a sphere of radius  4 is " + repr(volume_sphere("4"))

For the moment, we'll put the module in your class_files directory (so we don't have to change PYTHONPATH). Let's first make sure it can be called as a module. Make a copy of volume_sphere.py as volume.py. volume.py will be your module file (it will eventually hold all your functions that calculate volumes of solids; for the moment it only has the function volume_sphere). Then at the command line, do this:

# python -i volume.py
What is the radius of your sphere? 6
the volume of your sphere is 904.778684
the volume of a sphere of radius  1 is 4.1887902047863905
the volume of a sphere of radius -1 is -4.1887902047863905
the volume of a sphere of radius  4 is 268.08257310632899
>>> volume_sphere(0)	#run your own commands from the prompt
0.0

You told python to execute volume.py, which it did, asking you for a radius and returning a volume. Then python ran through the added tests and stopped (python had reached the end of volume.py) but python didn't exit; it stayed in immediate mode (the -i option; to see the difference run the same command without the -i option). Having loaded your file, python now knows about the new function volume_sphere() and you can run any extra tests e.g. volume_sphere(0).

Create and run this file (call_volume.py). It mimicks a piece of code that calls your module.

#! /usr/bin/python
from volume import volume_sphere

print
print "Testing code from call_volume.py"
print "the volume of a sphere of radius  2 is " + repr(volume_sphere(2))
print "the volume of a sphere of radius -2 is " + repr(volume_sphere("-2"))
print "the volume of a sphere of radius  0 is " + repr(volume_sphere("0"))

The new piece of code is the line

from volume import volume_sphere

which says "from the module file volume.py (or volume.pyc) import the function volume_sphere().

Note

The first time python imports your module file, it will make a bytecode version with a pyc extension, which it will subsequently use. Look for a new file volume.pyc in your class_files directory.

bytecode: a platform independant binary version of your .py file. It loads slightly faster, but runs at the same speed. You hand around the pyc file, if you want to make it difficult for the user to figure out the source code. (This is antithetical to the idea of GNU computing and the idea of the internet being a place to freely exchange information. A pyc file enables someone to make money by depriving you of information.)

How would you check that the py wasn't being used [38] ? How would you check that the pyc file was being used (in the absence of a py rather than the py file [39] ?

The output is

# ./call_volume.py
What is the radius of your sphere? 7
the volume of your sphere is 1436.755040
the volume of a sphere of radius  1 is 4.1887902047863905
the volume of a sphere of radius -1 is -4.1887902047863905
the volume of a sphere of radius  4 is 268.08257310632899

Testing code from call_volume.py
the volume of a sphere of radius  2 is 33.510321638291124
the volume of a sphere of radius -2 is -33.510321638291124
the volume of a sphere of radius  0 is 0.0

You asked python to load the function volume_sphere and then execute the code in the calling routine (print statements which call the function volume_sphere). You see the requested output in the 2nd block above (starting "Testing code ...").

The output starting "What is the..." is from code in the module file's global namespace. You did not ask python to execute this code, but it did anyway. Every module needs built-in testing code, but you don't want it executed every time you load the module. Handling this is the next step in building a module.

9.2. making a module of volume_sphere(): handling the global namespace code

In a normal language, code starts and ends execution in main(). Python is different: it starts executing with the first code it finds in global namespace. You would like your program to start execution in the same place, no matter what order you load your files, but python will start executing in a different place if you reorder your files. This is not what you want.

You could solve this problem by commenting out the global namespace code in your module, but then you'd have to write a separate file to test your module. The chances of keeping your module file and your module testing files together for decades is small. Your module has to be able to test itself and not rely on external code for testing.

The Python 2.5 Documentation (http://docs.python.org/download.html) in section 6.1 (More on Modules) says that the global namespace code in executed once on loading to allow initialisation of the function(s).

Note
compiled languages have a linker/loader to handle much of this
Note

What/why you'd do initialisation at load time (from a posting to the TriLUG mailing list - the poster didn't want credit, he said it was all common knowledge):

  • The most common case is a module will load more modules when loaded. That is, you import a module and the first lines in the module you load will be import statements which load yet more modules (and in turn, those modules may import still more modules).

    The module might selectively load certain other modules in a plug-in architecture based on things the module can introspect from the environment into which it was loaded.

  • You might want to cache some data from somewhere.
  • If you are doing metaprogramming in the module, you might want to create some new objects of the metatype you just loaded.
  • If you've defined some factory methods in the module, you might want to fire out some objects.
  • I think there's no end of uses for executing global namespace code when loading a module. I can think of no case where a module would be useful without executing code in its global namespace.
  • Basically, all the statements in a Python module are executed in order when loaded, whether as a script or as an import. The execution of most statements in a module, though, just define code objects (i.e., function objects, class objects, etc.) which will be used later (in the script, or by another script or module importing the module). So you might think of that as function and class objects getting "initialized" with their byte code.

There is a fix for code you don't want run from global namespace (it makes python execute in the same way that other languages execute their code).

First here's an normal module with its test code. You'll execute ./module.py.

#/usr/bin/python
#this file is called module.py
def function():
	#code for the function

#global namespace
#main() for this executable
#module initialisation code (if you need it)
#	eg if your function generated random numbers, 
#	you would seed (initialise) the random number generator (with say the date)
#	do that the random number generator would give different random numbers 
#	each time your ran the module

function(1)	#this is your testing code. You only want this to run when you execute the module by itself.

Python loads the function and then coming to the global namespace starts executing. What there is to execute is the line of test code function(1). You'll see the output from calling function(1) (whatever that may be).

output from function(1)

Next here's a bit of code that calls the function.

#!/usr/bin/python
#this file is called call_module.py
from module import function

#global namespace
#main() for this executable
function(2)

When you run the command call_module.py, function() is imported (loaded into memory, but not executed). As well (the bit you don't want) python starts executing in the first global namespace it finds, which is the line function(1) in the module. Next python will start executing in the global namespace for call_module.py, which is the call function(2). What you'll see will be

output from function(1) 	#you didn't ask for this (it's your testing code)
output from function(2)		#this is what you wanted

However the global namespace for module.py is not main() for call_module.py (the piece of code that's making all the calls). Once the code is in memory and just before it starts to run, here's what it looks like.

#module
def function():
	#code

#global namespace
#module initialisation code (if you need it)
#this is NOT main() anymore
function(1)
.
.
.

from module import function

#global namespace
#main() for this executable
function(2)

Since python runs global namespace code (whether it's in main() or not) whereever it finds it, you'll see the output from calling function(1) then function(2).

You apply the fix, a conditional, to the module file. It says "if this isn't main(), don't run it". Here's the fix.

#module
def function():
	#code

#global namespace
#module initialisation code (if you need it)
if __name__ == '__main__':
	#this is NOT main() 
	function(1)
.
.
.

from module import function

#global namespace
#main() for this executable
function(2)

Here's the output, with python now behaving like other languages.

#module initialisation code (if you need it)
output from function(2)		#this is what you wanted

The fix is a conditional statement

if __name__ == '__main__':

which says "If code execution starts here, then execute these indented statements". For the moment, you can regard syntax as magic (I do). The testing code is now governed by the conditional statement. If you execute the module by itself, the first global namespace code python will see will be in your module, it will also be main() for this executable, python will call this code "__main__" and the conditional will be executed. If code execution starts in global namespace code in some other file, (e.g. some routine calling volume_sphere.py) then the testing code in the module will not be called "__main__" and will not be run.

Here's the fixed version of volume.py containing the function volume_sphere.py.

#---------------
#! /usr/bin/python
#functions
def volume_sphere(r):
	"""name      - volume_sphere(r)
	   version   - 1.0 Feb 2008
	   method    - volume =(4.0/3.0)*pi*radius^3
	   parameter - radius as string, integer or real, valid all +/-ve numbers
	   returns   - volume of sphere, float
	   Author    - Homer Simpson, Homer@simpson.com (C) 2008
	   License   - GPL v3.
	"""

	import math     #import the whole math module (including lots of stuff you don't need)
	r=float(r)      #convert string input to float.
			#this allows the function to accept either a string or a number

			#pi in module math is called math.pi
	result=(4.0/3.0)*math.pi*r*r*r
	return result
#volume_sphere

#---------------
#main()

print "initialising volume.py"
print

if __name__ == '__main__':
	print "self tests for volume.py"
	print

	print "self tests for function volume_sphere()"

	##turn off the interactive test(s)
	##get input
	#resp = raw_input ("What is the radius of your sphere? ")
	##call the function, passing a string
	#volume=volume_sphere(resp)
	##formatted output
	#print "the volume of your sphere is %f" % volume

	#make a few calls to volume_sphere() to test it
	print "the volume of a sphere of radius  1 is " + repr(volume_sphere(1))
	print "the volume of a sphere of radius -1 is " + repr(volume_sphere("-1"))
	print "the volume of a sphere of radius  4 is " + repr(volume_sphere("4"))

# volume.py ---------------------

I've added code in the place where you would initialise the module on loading ("self tests...).

Here's the module being run in self testing mode (note a string indicating initialisation, a string indicating that file volume.py is being run, and a string indicating that tests are being run for module volume_sphere).

# ./volume.py
initialising volume.py

self tests for volume.py

self tests for volume_sphere()
What is the radius of your sphere? 7
the volume of your sphere is 1436.755040
the volume of a sphere of radius  1 is 4.1887902047863905
the volume of a sphere of radius -1 is -4.1887902047863905
the volume of a sphere of radius  4 is 268.08257310632899

Here's the module being run in interpreted mode. You can run your own tests at the end (here we outputted the volume for a sphere of radius "0").

# python -i ./volume.py
python -i ./volume.py
initialising volume.py

self tests for volume.py

self tests for function volume_sphere()
What is the radius of your sphere? 7
the volume of your sphere is 1436.755040
the volume of a sphere of radius  1 is 4.1887902047863905
the volume of a sphere of radius -1 is -4.1887902047863905
the volume of a sphere of radius  4 is 268.08257310632899
>>> volume_sphere("0")
0.0

Here's the module being called from another piece of code. Note that the initialisation code (before the "if" statement) is still run, but that now the self testing code is not run.

# ./call_volume.py 
initialising volume.py


Testing code from call_volume_sphere.py
the volume of a sphere of radius  2 is 33.510321638291124
the volume of a sphere of radius -2 is -33.510321638291124
the volume of a sphere of radius  0 is 0.0

Summary: make the module self testing. That way a developer, unsure of a problem they're having can quickly re-run self tests on suspect modules. (People do accidentally change the wrong files, or "upgrade" them.)

9.3. Code Maintenance

By now I hope you realise

  • Error messages are somewhere between wrong and right, and you can't tell which by looking at them.
  • It's hard to find your own bugs.

    Brian Kernighan: "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."

  • It's harder to read code than to write it (from Things you should never do, Part 1 http://www.joelonsoftware.com/articles/fog0000000069.html). This makes difficult the goal of software reuse.
  • It's hard to debug other people's code. The code I wrote for volume_sphere.py had intentional mistakes. Being new to python, you couldn't tell that the code was wrong. But even if you've been coding for years, any code that looks about right, is assumed to be right and you won't know there's a problem till you run it.

    Everyone's code is different. Educators wondering how they're going to detect if students have colluded in writing their homework assignments, are surprised to find that every piece of code is different. Why? Well there's only one way to code up this problem, right?

Much code is short programs that are used a few times and then thrown away. People don't document them well (or at all) and usually don't get into too much trouble for the lack of documentation.

Other code will be upgraded, or used unmodified for decades. The original author may be long gone, while the code is run hundreds of times a day, or once a year.

It turns out that the major cost in writing software, is not the initial cost of producing the first working version or even the production version, but maintaining the code (over decades). Someone who's never seen the code, doesn't know what it does or how it works, will be assigned the job of fixing/updating your code. It may take months before they can make the first changes. This may be longer than it took the original author to write the code. The prime aim of the coder should be to write code that a human can read.

Because code is unmaintainable, it's often simpler to write a new version from scratch. This is a terrible waste of resources. Managers, who often know nothing about writing code, are willing collaborators in this tradgedy. They will be sucked in by the arrival of a new language and say "this code was written in (some old language), it's time for us to be running (this year's fancy new language)". The effort to re-write the code from scratch in a new language is seen as a development cost. It's not, it's maintenance and is unneccessary maintenance at that. The old code was already debugged, an enormous investment; you can't throw away tested and debugged code. The computer doesn't care about the language the code was written in. Adding two numbers is the same to the computer, whether it was written in Fortran or Java. The new version will often be functionally worse than the original (and no more maintainable), but management will not realise this. To justify the money and time spent on the rewrite, management will tout the code rewritten in this year's new language, as an advance.

You code must be documented, and easy to read. Because reading other people's code is hard, anyone can write compact, fiendishlessly smart and cryptic code, that no-one else can read and that even the author won't understand 6 months later. Don't be too smart: make your code simple. Your first objective is to write maintainable code. Your prime objective in writing code, is not that the code works (which it must), or that it works correctly (which it must) or that it's fast (nice, but not required), but that it's maintainable.

Most GPL packages now have testing code which is run before installation.

Once your managers realise you have working code, they can claim their money from the customer, and they'll want you to move onto something else (which will make them money). You'll get little support for writing documentation and testing routines. When someone says to you "it works? then we don't need testing routines" you're talking to someone who doesn't know how to code and doesn't understand the enormous cost of maintaining bad code. You may have to do what they say, but you don't have to agree with them.

In the module volume, one of the functions does an import math to get the value of π. If you have multiple functions each import'ing math, you could move this instruction to the module global namespace initialisation area, so math only has to be imported once. Your module would work fine, but you shouldn't do this. Why [40] ?

Note
End Lesson 12

9.4. Functions: recap

Functions are an essential part of a program. Functions look much the same in all languages and are invoked the same way (by passing parameters/arguments and accepting a returned variable). Most action in a program will be in functions. Any self contained logical block in a program is a candidate for being made into a function. Functions can be called from anywhere (i.e. from main() or from other functions).

The calling routine passes parameters; the called routine is passed parameters. The name of the parameters in the calling routine is usually different to those used in the called routine. The use of different names is for readability. The name of the parameter passed from the calling routine is usually specific to the calling routine. The name used for a parameter in the function is generic, a name that will work no matter what it's called by. If the same name is used for a parameter in the calling function and in the called function, then there are two variables, each in their own scope, with the same name: changes to the variable in the called function do not affect the variable of the same name in the calling function. The list of parameters is often called the interface - it's the exchange of information that must occur for a function to work.

#! /usr/bin/python

#my_program.py

#functions

def my_function(param1, param2..paramn):
	#calculate result
	return result

#main()
	#get data
	#do calculations
	#sample function call
	my_function(variable1, variable2..variablen)
	#output results

main() should be short, with as much work as possible handed off to functions. Functions can be called at any step in main(). Often the control of the flow of the program is complicated, in which case main() will be longer.

Code is turned into a function for

  • modularity - so a piece of code can be replaced by another piece, that behaves the same to the calling routine. Improvements in the program are made one function at a time. After the program is working, rarely is the interface (the parameter list) between a calling function and the called function changed.
  • readability - functions are small (5-50 lines approx). Their purpose and the implementation can be understood quickly and easily.
  • scope - variables declared inside a function cannot be seen by the calling function (or by most functions). Except for the parameters passed, and global variables, a function cannot access any variables in the program.

Functions must have documentation, enough for someone reading the docs to be able to recreate your code without having seen the code (for an example see ???.

9.5. Function example: volume_hexagonal_prism()

Continuing our exploration of solid objects, write a module to calculate the volume of a hexagonal prism.

A prism Prism (Geometry) (http://en.wikipedia.org/wiki/Prism_%28geometry%29) is an n-sided polygon translated through space. A cube (and a rectangular block) is a prism, because (at least one) of its faces is the same as you slice off pieces parallel to the face. A cylinder is not a prism because the face is a circle, not a polygon. An ice crystal is a prism: it has a regular hexagon base and extends at right angles to the base. Many crystals are prisms.

Hexagonal ice prisms in thin layers of cirrus clouds (in NC seen mainly in winter) are responsible for spectacular ice rainbows and halos e.g. Frequent Halos (http://www.atoptics.co.uk/halo/common.htm), The Cloud Appreciation Society (http://cloudappreciationsociety.org/version2/wp-content/uploads/2008/02/08-feb-high1.jpg) and sundogs seen in a circle of radius 22 from the sun (see 22 halo http://en.wikipedia.org/wiki/22%C2%B0_halo).

The formula for the area of a regular hexagon (in algebra) from Areas and Perimeters of Regular Polygons (http://www.algebralab.org/lessons/lesson.aspx?file=Geometry_AreaPerimeterRegularPolygons.xml) and Area of a Regular Polygon (http://www.mathwords.com/a/area_regular_polygon.htm) is

Area = ((3*sqrt(3)/8)*d^2	#d=vertex to vertex (diameter of circumcircle)
Area = ((3*sqrt(3)/2)*r^2	#r=center to vertex (radius of circumcircle) 
Area = 2*sqrt(3)*apothem^2	#apotherm=cente to face (radius of inscribed circle)
Area = (2*sqrt(3)/4)*face-to-face-diam^2
Note
In python the square root can be calculated a couple of ways
>>> number=2
>>> number**(1.0/2.0)
1.4142135623730951

>>> number=9
>>> from math import sqrt
>>> sqrt(number)
3.0

Write a self contained file volume_hexagonal_prism.py (later you will then turn the file into a module and add it, along with self tests, into volume.py). Here are the specifications:

Note
The simplest thing to do would be to copy volume_sphere.py to volume_hexagonal_prism.py and start hacking on that code. You never write code from scratch if you have something similar, that's debugged and tested.
  • The main() code
    • asks the user for the vertex-to-vertex diameter of hexagonal base
    • asks for the height (or length, you choose what you call it) of the hexagonal prism
    • passes these two parameters to the function volume_hexagonal_prism(d,h)
    • prints the results using unformatted output in the following format
      The volume of a hexagonal prism of diameter xxxx and height xxxx is xxxxx
      
  • The function volume_hexagonal_prism(d,h) does the following
    • has comments and documentation (do that after you get it to work)
    • does any conversions on the input parameters neccessary for the function to use them
    • calculates the area of the base using the information above
    • calculates the volume using Volume=Area of base * height
    • returns the volume to the calling code

Check your output using a few test inputs. Make the test code appropriate for the volume_hexagonal_prism() case. Finish by commenting out the tests requiring user input and check that the non-interactive tests run OK. Check that the output is equivalent to running the volume_sphere() code in volume.py.

Remember the formula I gave you for the volume of a sphere that contained "(4/3)" which the computer evaluated as an integer, and the formula gave the wrong result? Always check your answer by hand. You might think that you could plug the numbers into a 4 function calculator and get the right answer and in this case you likely will. However one day you'll make a mistake without knowing it. The people who built the Hubble Space telescope, did a complicated test on the mirror surface and got the wrong answer. An amateur telescope maker using a knife edge and a flashlight (torch) bulb (the Foucault Test, http://en.wikipedia.org/wiki/Amateur_telescope_making#Foucault_test) would have detected the problem in a matter of minutes. The simplest test wins.

For a complicated shape like a hexagon, you have to think a bit to get a simple calculation that gives a good enough answer. First check the area of a hexagon. Here's two checks

  • Here is an ascii art illustration of a regular hexagon sitting inside a square (whose area you can calculate easily) (as depicted, the side of the hexagon at the top would not quite touch the box, but we just want an approximate area here).

     ___
    |/ \|
    |\_/|
    

    What should be the area of a regular hexagon of vertex-to-vertex diameter=1 (left-to-right distance) compared to the square of sides=1? Less than 1? Less than 1/2? More than 1 [41] ? Is the area more than 1/2? The object with half the area of the original square would be a square tilted at 45 to the original square and whose vertices are at the midpoints of each side of the original square (the two squares would look something like this).

     __
    |/\|
    |\/|
    

    The hexagon joins the square at the midpoints of the vertical sides of the square, but the hexagon's sides join the horizontal sides of the square (the top and bottom) outside the center of the horizontal sides. Is the hexagon bigger or smaller than the square of area 0.5 [42] ?

    You should expect the area of a hexagon to be less than the area of the outside (enclosing) square, but more than the area of the enclosed square (which has an area of half) i.e. 0.5<area hexagon<1.0.

  • The area of the circumcircle (the circle which touches all the vertices and with the same center as the hexagon) is what [43]? What can you can about the area of the hexagon touching the circumcircle [44] ?

You now know that the area of a hexagon of vertex-to-vertex diameter=1 is between 0.5 and 0.78.

Check the volume of a hexagonal prism with height=1. Then try combinations of height/diameter of 0,1. Check the change in volume if you double the height, double the diameter (by what ratio should the volume change for a doubling of the base diameter, a doubling in the height?).

Here's my code for volume_hexagonal_prism.py together with the output from three pairs of checks run from the command line. [45]

The print statement in main() does not have repr() for diameter and height, but does have it for volume. Why [46] ?

Note
End Lesson 13

9.6. making a module of volume_hexagonal_prism()

You're likely to write lots of functions. You put them in modules with similar functions. Here we're going to put the function volume_hexagonal_prism() into the module volume We've already put the function volume_sphere() into the module volume. Let's look at what happens when we add a 2nd function to the module.

The simplest scheme is to copy/paste volume_hexagonal_prism.py into volume.py this way.

Note
This code is called pseudo code - it's half English, half computer code; it's sufficiently generic that it could be implemented in any computer language.
shebang (if needed)
#scheme 1, for small modules

	"""
	collection of functions that calculate the volume of solids
	Authors attributed in each function.
	"""

volume_sphere()
	#code

volume_hexagonal_prism()
	#code

#----------
#main()

global namespace initialisation code

if __name__ == '__main__': 

	tests on volume_sphere		#multiple lines
	.
	.

	tests on volume_hexagonal_prism	#multiple lines
	.
	.

#--module volume

If you do something once, you can do it almost anyway you want. As soon as you do something twice, (like having two functions in a module) you have to consider what will happen if you do it 100 times. This is the problem of scaling mentioned in ???.

Inserting the functions is easy - you just stack them in order from the top of the file. It's what to do with the tests that's the problem. If you have 10 functions, each with 10 lines of tests (and comments) which you move into the module's global namespace (at the bottom of the file), you will have 100 lines of tests. This is too long a block of code to read and now the tests are a long way from the code they're testing. You'll have to do some thinking if you ever want to modify the file (like to turn off some of the tests).

On looking at your code with two functions, programmers will first ask "but does it scale?". Most often there aren't elegant solutions; mostly there are solutions that people will accept (or put up with). More often than you would like, the best solution anyone can think of is plain downright ugly (that's life).

You have some latitude on what to do here. Your main idea is for someone else to be glad to read your code.

One of the principles of code writing is that when a function (or main()) becomes too big, you split out a self contained logical block into a function. To do that here, put the tests for each function into their own test_function(), and call the test_function()s.

shebang (if needed)
#scheme 2, for medium sized modules

	"""
	collection of functions that calculate the volume of solids
	Authors attributed in each function.
	"""

volume_sphere()
	#code

test_volume_sphere()
	#tests

volume_hexagonal_prism()
	#code

test_volume_hexagonal_prism()
	#tests

#----------
#main()

global namespace initialisation code

if __name__ == '__main__': 

	test_volume_sphere()		#one line
	test_volume_hexagonal_prism()	#one line

This will work for 100 functions - you'd have 100 lines of test_function_name() at the bottom of the file. People would realise that this was a big module file and that the calls at the bottom are just a list of calls to test functions. Once the number of lines of calls in main() becomes unmanageable, you collect them into a module.

shebang (if needed)
#scheme 3, for huge modules

	"""
	collection of functions that calculate the volume of solids
	Authors attributed in each function.
	"""
volume_sphere()
	#code

test_volume_sphere()
	#tests

volume_hexagonal_prism()
	#code

test_volume_hexagonal_prism()
	#tests

run_all_tests():
	test_volume_sphere()		#one line
	test_volume_hexagonal_prism()	#one line
	.
	.

#----------
#main()

global namespace initialisation code

if __name__ == '__main__': 

	run_all_tests()

If you've got 100 functions, this 3rd way would be the best.

How do you know whether you have a small, medium or huge module? If you (or the users) can't understand the mess of code in main(); if they can't, you need to go to next scheme.

For the moment, let's make the module using Scheme 2.

Here's the specificiations

  • copy the function volume_hexagonal_prism() into volume.py, putting it below the function volume_sphere().
  • Make functions test_volume_sphere(), test_volume_hexagonal_sphere() and move the testing code from the two main()s into the appropriate test function.
  • Call the two test functions from code in main()
  • Set up the conditional "if __name__ == '__main__':" to only call the tests if volume.py is run as a standalone program (rather than loaded as a module by another file).

I've commented out the interactive tests, so the tests will run without keyboard input. Here's my version [47] and here's the output

# ./volume.py
initialising volume.py

self tests for volume.py

self tests for function volume_sphere()
the volume of a sphere of radius  1 is 4.1887902047863905
the volume of a sphere of radius -1 is -4.1887902047863905
the volume of a sphere of radius  4 is 268.08257310632899

self tests for function volume_hexagonal_prism()
the volume of your hexagonal_prism of diameter 1.0 and height 1.0 is 0.649519052838329
the volume of your hexagonal_prism of diameter 2.0 and height 1.0 is 2.598076211353316
the volume of your hexagonal_prism of diameter 1.0 and height 2.0 is 1.299038105676658
Note
End Lesson 14

9.7. Do the tests give the right answers?

A person installing your module and running the above tests has no idea if the output is correct. We have to compare the answer found with the expected answer, then output a pass/fail message. The user doesn't have to see the actual numbers (although they can).

Comparing reals is a problem (as we'll see in ???). The number on the screen (or in your code) may differ by a couple of low order bits from the number in memory. The number "0.1" will actually be represented by "0.10000000000000001". You have to compare the ratio or find the difference. The ratio (division) test will fail if the divisor is "0.0", so it's probably best to test the difference between the expected and found numbers. Reals in python are 64-bit giving a precision of 2-52. The numbers output by the test will either be as correct as the computer can make them, or will be obviously wrong.

Let's say to be safe that we'd like the difference between the numbers to be 2-50 (giving us 2 bits margin of error in detecting whether there is an error). What's 2-50 in decimal [48] ?

Note
You all know what 232 is [49] . Here's some more useful numbers
2^10  = (approx) 10^3   = 1k
2^20  = (approx) 10^6   = 1M
2^30  = (approx) 10^9   = 1G
2^40  = (approx) 10^12  = 1T
2^50  = (approx) 10^15  = 1P

2^-10 = (approx) 10^-3  = 1m
2^-20 = (approx) 10^-6  = 1u
2^-30 = (approx) 10^-9  = 1n
2^-40 = (approx) 10^-12 = 1p
2^-50 = (approx) 10^-15 = 1f
Hard disk manufacturers have been successfully sued for labelling disks as having (for example) 1TByte of storage, which people expect to mean 1099511627776 (1.1*1012) bytes, when they have only 1012 bytes (10%less). Attempts to diambiguate 1024 and 1000 as a kilobyte have lead to an alternate nomenclature, which is unfortunately is clumsy (Mebibyte http://en.wikipedia.org/wiki/Mebibyte).
Note
For the real way to compare numbers see Lahey Floating Point Arithmetic (see the section on "Safe Comparisons") (http://www.lahey.com/float.htm).

The test requires the numbers to be the same at an accuracy of 10-15, which in turn requires the output from your code to display 16 significant figures after the decimal point. If your output has only 12 significant figures (the default for the python print statement which uses str() which in turn prints 12 significant digits, see Floating Point Arithmetic, http://docs.python.org/tut/node16.html), the test will erroneously return fail. We will explore this more in ???. For the moment note

>>> j=0.12345678901234567890	#j has 20 digits after the decimal point
>>> print j
0.123456789012			#standard python printing has 12 digits
>>> j
0.12345678901234568		#64 bit real has about 16 significant digits
>>> print "%5.20f" %j		
0.12345678901234567737		#64-bit real printing 20 digits and showing garbage after 16 digits
>>> 

Check that your tests in volume.py give output with 16 figures after the decimal point before proceeding.

Start a file test_compare_results.py with a function compare_results() which has these specifications

  • takes two paremeters n_expected, n_found
  • compares the difference between these two numbers with the allowed error for a 64-bit real

    Note

    If your test is a < (less than) inequality test, and if the difference between the two numbers is +ve, you'll get a valid test. What will go wrong if the difference is -ve [50] ?

    Assume in one case that the two numbers are (1.0, 1.0000000000000001), and in another case are in the opposite order i.e. (1.0000000000000001, 1.0). How do you handle the case when the difference is -ve?

  • returns a string "pass" or "fail" for the comparison
  • test your function from main() with these calls
    print compare_results(1.0, 1.0000000000000001)
    print compare_results(1.0000000000000001, 1.0)
    print compare_results(1.0, 1.1)
    print compare_results(1.1, 1.0)
    

Here's my code [51] and here's my output [52] . Did one of the last pair pass? If so look at the note in the 2nd bullet above.

Why did I test the pairs of numbers, twice, the 2nd time in the reverse order [53] ? Why did the first pair pass, while the second pair failed [54] ?

The parameters passed to compare_results() are reals. How would you change the function to handle parameters than were string representations of numbers [55] ? (You don't need to handle this here. The numbers you're comparing are generated by the computer and will be numbers and not strings.)

Note
End Lesson 15

Shortly we will put compare_results() into volume.py, but first we can test it interactively (before we risk messing up volume.py by changing the code). Here's the interactive code

# python -i test_compare_results.py
pass
pass
fail
fail
>>> from volume import volume_sphere
initialising volume.py

>>> print "the volume of a sphere of radius  1 is " + repr(volume_sphere(1))
the volume of a sphere of radius  1 is 4.1887902047863905

In the interactive code we

  • ran test_compare_results.py:

    The python interpreter always runs the code in global namespace, which in this case has tests of the function compare_results(). Since test_compare_results.py is being run/executed, the python interpreter treats the global namespace as being main(). There are no tests in the global namespace to differentiate main() from the other code in global namespace, so all the code in global namespace is run.

  • loaded volume_sphere():

    Again, the python interpreter always runs the code in global namespace. What is the result of running this code [56] ? The global namespace in volume.py has code which runs tests on volume_sphere(), but this code is inside the conditional statement

    	if __name__ == '__main__':
    

    and it not run (why not [57] ?)

  • ran one of the tests built into test_volume_sphere()

We want to modify this test to compare the output with the expected output. At the end of this last line, add a call to compare_results() to compare the output with the expected output, so as to print a pass/fail [58] .

Notice how the symbol "1" and "4.1887902047863905" are used multiple times and/or they are the arguments to expressions? Constants (numbers that aren't ever going to be changed by the program) should be assigned to variables and the variables used in expressions. The reasons for this are

  • Constants determine the the behaviour of the code. They should be at the top, assigned to variables, with comments, so that someone doing maintenance knows what they're about and knows under what circumstances they can and cannot be changed.
  • A person reviewing the code has no idea whether a "1" here, is the same number as a "1" somewhere else. Is one of them my_variable or both? Is the "2" my_variable+1, or is it my_variable*2, or is it something else alltogether?
  • You can't put comments in an expression.

Assign the constants to variables (e.g. radius, expected_result) and use the new variable, rather than a number, in the expressions. Here's my code [59] .

You have two calls to (e.g. volume_sphere(radius)) in this print line. You never calculate anything twice: instead assign the result to a variable and use the value of the variable twice. Assign the value of the volume to volume_found and rerun the test. Here's my code [60]

Here's the final version, which you developed using python interactively

# python -i test_compare_results.py
python -i test_compare_results.py
pass
pass
fail
fail
>>> from volume import volume_sphere
initialising volume.py

>>> radius=1
>>> expected_result=4.1887902047863905
>>> volume_found=volume_sphere(radius)
>>> print "the volume of a sphere of radius " + repr(radius) + " is " + repr(volume_found) + " " + compare_results(expected_result, volume_found)
the volume of a sphere of radius 1 is 4.1887902047863905 pass
>>> 

volume.py consists of the functions we're really interested in, volume_sphere() and volume_hexagonal_prism(), plus a matching pair of functions, which test the functions with various test inputs. However as yet we don't have any way to test if the output is correct. You now are going to merge the code from test_compare_results.py and the interactive code above, into volume.py, so that you can compare the output of the tests with the known answer.

  • test_compare_results.py consists of a function compare_results() and some code in global namespace to test compare_results. What code from test_compare_results.py will be merged with volume.py and where will it go [61] ?
  • The interactive code above is a mock up of the changes that we're going to make to what part of volume.py[62] ?

Now do the merge

  • copy volume.py to volume_2.py
  • add/copy the function compare_results() from test_compare_results.py to volume_2.py
  • rewrite your tests, using the above template, to show pass/fail.
    Note
    Start by modifying test_volume_sphere(). You have 3 tests in each of the test functions: modify one of the tests and get it working first, then tackle the other 5 tests.

Here's my module volume_2.py [63] and here's the output [64] .

You are now safe to let modules out into the world. Congratulations.

Note
End Lesson 16

9.8. Code clean up

Note

I didn't use this material in class, as I couldn't see any teaching value in it. It's left here to show that sometimes there isn't a way to write good code.

The same line of code

print "the volume of a sphere of radius " + repr(radius) + \
	" is " + repr(volume_found) + " " + compare_results(expected_result,volume_found)

is used in multiple places in the tests, no matter what values are passed to the test. It's not to difficult for a reader to guess that the lines all might be the same (the text lines up from line to line), however it would take a bit of checking to be sure. If you wanted to change the line, you would have to do it multiple times (this is not good, from the point of view of maintenance, you might miss one line). It would be better to have only a single copy of the line of code. You could call it in a function, but a function with only one line of code, whose only purpose is to output a string for the user, is a bit pointless.

Instead I rewrote the code to run the print statement inside a loop, using the loop to feed in the parameters. After looking at the resulting code, I couldn't see that functionally is was any different to calling a function. Since the normal idiom is to call a function, anyone trying to maintain the code would have to puzzle as to why it was written in a loop.

I decided that for the number of times the tests would be run, that it really didn't matter if it was left the original way. Sometimes bad code is OK enough, or at least not worth the trouble of fixing.

We have several parameters (radius, expected_result) to feed to the line. However loops feed parameters one at a time e.g.

for item in items:
	#do something to item

Let's group the parameters into a list, which as far as a loop is concerned is a single object. Each iteration of the loop will feed a list to the loop code. Since we're doing multiple tests, we'll need to feed many lists, so let's have a list of lists.

Before doing anything with lists, look at this info about ??? and ???.

Here parameter_list[] is a list of reals. loop_list[] is a list of parameter_list[]s (i.e. a list of lists). The code for using lists to feed parameters to our tests is

#construct lists from input data
loop_list = []	#initialise list

#first test
parameter_list = [ radius_1, expected_number_1, volume_1 ]
loop_list.append(parameter_list)		#add parameter_list to the tail of loop_list 

#second test
parameter_list = [ radius_2, expected_number_2, volume_2 ]
loop_list.append(parameter_list)		#add parameter_list to the tail of loop_list 

#we're reusing the variable parameter_list here. It's reinitialised in this statement
for parameter_list in loop_list:		#recover parameter_list one at a time
	volume = parameter_list.pop() 	#pop() removes from the tail, so last in is first out
	expected_number = parameter_list.pop() 
	radius = parameter_list.pop()
	print "the volume of a sphere of radius " + repr(radius) + " is " + repr(volume_sphere(radius)) + " " + compare_results(expected_result,volume_sphere(radius))

Modify your code to run your tests in a loop (copy volume_2.py to volume_3.py). Here's my code [65] .

To add (or modify) a test, it's just a matter of adding (or modifying) a paragraph like this

	diameter = 1.0
	height = 2.0
	expected_result=1.299038105676658
	volume=volume_hexagonal_prism(diameter, height)
	parameter_list = [diameter, height, expected_result, volume ]
	loop_list.append(parameter_list)
Note
I wouldn't say that this version of the tests with a for loop is particularly more readable or modifiable than the previous version. However it's an alternate way of handling the scalability problem.

9.9. Train Wreck code

Code in compare_results() looked a little like the following. Notice any difference in readability between these two functionally identical pieces of code?

expected_result=1.0
volume=call_to_function(param1,param2...paramn)
compare_results(expected_result, volume)
expected_result=1.0
compare_results(expected_result,call_to_function(param1,param2...paramn))

The second line of the last piece of code is called a train wreck. It's not too big a train wreck as far as train wrecks go, but it's still a train wreck.

It's very easy, once you've set up all your functions, to have lines of code like this, that have calls to functions to any depth you can imagine (the params themselves could be calls to functions). The code is compact and demonstrates recursive parsing of code.

Note
Recursive parsing is the ability to recognise an expression, which will evaluate to a number, as being equivalent to a number. Early languages (e.g. Fortran) could not do recursive parsing. If instead of a number, an expression which evaluated to a number was found, the compiler/interpreter would crash. This was really stone age computing.

Compact code makes a section of code look neater (there's less lines of code). This is all very nice, except that code like this is hard to read and no-one (including you in 6 months), will have a clue what it's doing. The next person won't look forward to fixing it if it stops working.

9.10. Review: Modules

Python has an idiosyncratic model for global namespace. I had you go through it in class to understand how to write testing code for modules. While being able to understand it, when it's explained to you is useful, remembering how to write code to test python modules is of no value when learning programming. If you were a python module programmer, you'd start with a sheet of instructions and follow them. No other language requires special attention to namespace when testing modules (elsewhere called libraries).

Make a module to do cruise control. Since you already have a file cruise_control.py you need another name. Copy cruise_control_3.py to cruisecontrol.py and to run_cruisecontrol.py (two files; you'll be deleting parts from both files to divide the functionality of cruise_control_3.py). Delete code from both files so that cruisecontrol.py has the code which is concerned with cruise control and run_cruisecontrol.py has the code which turns the cruise control on/off. If you've thought about how to split up the code for a while and need a hint [66]

answer: [67]

10. Giving a Seminar

You have now written a module (volume.py) that is

  • tested and debugged (i.e. you know it works)
  • self testing (anyone else can verify that it works)
  • documented

It meets all the criteria for a module that can be released to the world. If you'd written this code in an educational or work environment, you'd be expected to give a seminar on your code.

I now want you to explain the function of the module volume.py. The point of this is

  • to talk in front of people
  • to coherently convey information
  • to answer questions on your feet

Since you've written and debugged the code, you have little to fear from the audience - you know more about the code than they do and they aren't likely to trip you up. You will have to explain things they don't understand and that you do.

10.1. Explaining the function of code: Separate "what" from "how"

When writing code, make sure in your mind that you separate what the code does from how you implement it.

  • what: parameter(s), return value, the specifications of what the function/piece of code must do.
  • how: the instructions, algorithms used, language used to code it.

You should be able ask someone to reimpliment the function (change the how), changing the code, without changing the what. Anyone using/testing the function should not be able to tell that the code in the function has been changed underneath them.

Here's an example piece of code from compare_results()

	difference = n_expected - n_found

	if (difference < 0.0):
	        difference *= -1.0

If you were explaining this code, you'd say that the function of this piece of code is to calculate the magnitude of (size of) the difference between n_expected,n_found. Depending on your audience, you may have to explain why you want the magnitude of the difference, or you may not. You would not explain what each line of code was doing - doing so is explaining the implementation and not the function. (In some circumstances, you might want to explain the implementation, but assume you're showing the code to a programmer - in this case you'll be explaining the function.) When explaining the function, you allow for the possibility that someone else will reimplement your code. Next time you see the code it might look like

	difference = mag_diff(n_expected,n_found)

and your explanation would be the same.

It should be possible for someone to reimplement your code in a computer language you've never seen, using a character set you don't know (Chinese, Cyrillic, Arabic) and for you to come into a room full of people who know this character set and computer language and for you to give the same explanation of how the code works, even though you can't read the displayed code.

People may ask you to explain your implementation of a function, in which case you'll have to justify it, or listen to a better one from the audience. Part of the reason you give a talk is to find out from people who know more that you. You should accept such help with humility: it's far better to find out from your friends or co-workers, than your rivals or the purchasing public, who decided to stay away from your product in droves.

Quite often there's a dozen ways of implementing something, all of which are equivalent. Instead of

	if (difference < 0.0):
	        difference *= -1.0

you could have written

	if (difference < 0.0):
	        difference = -difference

If you've thought about the other ways of implementing this (and before you give a talk, you should have) you'll know your way is as good as any other and you can just say "it was the first method I thought of" and leave it at that.

You should plan on describing the function of the code. Only explain the implementation if someone requests it, or you've done something really nifty. Be carefull though - people are wary of really nifty code - and may regard it as code that will be unmaintainable when you move to another project.

10.2. The Seminar Environment

Seminars have a standardised format. You can go anywhere in the world, to a seminar on any topic and the format will be exactly the same.

  • There will be an audience, arrayed before you in chairs. In normal workplaces, attendance is voluntary and people will attend because they're interested in your talk. In workplaces where the management don't trust the intelligence of the workers, and the productivity is poor, attendance will be required and the audience will at best be polite or sullen prisoners.
  • There will be an organiser. It will be that person's job to introduce you (tell some interesting facts about where you work now or might have worked before hand, and other things you've done relevent to the work you're going to present).

    Something that's occassionally done which I don't personally like, is that the organiser will say something about your personal life, that's completely irrelevent to your talk (e.g. your golf score). I haven't come to hear about golf and this is a waste of my time. The purpose of this is to reassure the audience that the genius in front of them is a normal person too. I don't care if you're a normal person - you can have 3 heads for all I care - I've come to hear the talk. You usually only find this at places where audience attendance is compulsory.

  • Usually you (or your work) will be known by at least the organiser, if not by many people in the audience, or there would be no reason to bring you in for a seminar.
  • You will be on a podium (raised up from the audience, so the people at the back can see you). You will have a lectern (tilted stand where you put your notes). You will have controls to the projector, sound and other visual aids. Depending on your talk, you may have a view graph, or as is most common now, you bring your laptop with your presentation material, which you plug into the video cable of a computer projector.
  • Once you start, you're on your own - you run the show. You will always have a time limit, which can be a loudly proclaimed 10-15 mins in a conference, or an undeclared upto 1hr in a departmental weekly seminar. No matter what, you cannot overrun your time - it's the most discourteous thing you can do. People have things to do and have to figure out their time budgets. A 1hr seminar includes the moving chairs around and introductions at the beginning; it includes question time at the end. The audience must be out the door an hour after they arrived. This gives you 45-50mins to talk. No-one knows enough to demand more than an hour of anyone's time.
  • By the end of the seminar, people in the audience will have sized you up and will be given a chance to meet you. The organiser will say "a couple of people have asked if they could talk to you. Would this be OK?" or "Do you have time for that?". You do. He will have a list of people and the times they can meet you. Normally these people will ask questions to help them with their work.

10.3. Speaking

There a couple of rules

  • Low ideation words are not allowed, including
    • "um", "ar".

      You are allowed to stop and think of what to say next. Complete silence is perfectly fine (as long as you don't look flustered). People know you're saying nothing. If you say "um", people have to listen to it and recognise that you're saying nothing and throw it away. This is tiring and a waste of people's time.

    • "like", "pretty" (as in "pretty big") and the big one "very". "Very" says nothing. You shouldn't use it in talks or in writing. (Newspapers don't use it).

      Substitute "damn" every time you're inclined to write "very"; your editor will delete it and the writing will be just as it should be. Mark Twain (http://www.brainyquote.com/quotes/authors/m/mark_twain.html)
  • You can't read your text. Read material sounds dead and is hard to pay attention to. You must speak from memory or be able to construct it on the fly (it doesn't matter which).

    Some people can construct whole pages of material extemporaneously, and some people can't. If you can't (I can't), then it's likely that you won't ever be able to do it. In this case, a week or two ahead of your seminar, you're going to have to start rehearsing it, reading it out loud, till the sentences sound right, and until you know it off by heart. When you get to the seminar, you'll occassionally have to glance at your notes to remember the next point, but you'll be doing it from memory and it will sound right.

    In a seminar, you aren't thinking of (constructing) your material in real time, so you can speak much faster than you would in conversation (where you have to think of what to say and how to respond). The higher speed does sound strange initially, but this is not a lesson, where the audience has to think a whole lot. You'll be telling them material, much of which is familiar, and the audience will be spending most of its time saying "yup got that".

    Two examples of speakers:

    • The 2 time Nobel Prize winner Linux Pauling (who I've heard speak). He gave a most exciting talk, and although I knew much of the material, he talked about it as if he'd only just learned about it. I assumed I was hearing new stuff. I told this to my PhD supervisor, who was also at the talk, who smiled in agreement "I heard the exact same talk 30yrs ago and it was just as exciting then too". I presume Linus Pauling is one of the people who can talk extemporaneously.
    • The american military figure General Douglas MacArthur (who I've not heard speak, but I've read several biographies). He was an inspiring speaker, and everyone assumed that this was a natural talent. In fact MacArthur would spend hours practicing his speaches, till he got every word and sentence to have the meaning and emphasis he wanted. I assume MacArthur was one of the people who can't speak extemporaneously. MacArthur's farewell speach at West Point in 1962 is available in streaming format at MacArthur (http://www.hughcox.com/MacArthur/). The speed in text form is at General Douglas MacArthur's Farewell Speech: the long gray line has never failed us (http://www.freerepublic.com/focus/f-news/1068922/posts).
  • Assume the audience has reasonable background knowledge of the area of your talk. You don't have to explain too much background material.
  • Assume the audience is intellegent. Just give them the facts. You don't have to interpret the work ("this stuff is amazing"), they know enough to figure it out themselves.
  • You will likely be asked questions during the talk. Spending too much time on these will wreck your time budget for the talk, so some speakers ask that questions be left to the end. However you can't expect an audience member to follow your talk, if there's something they don't understand early on in the talk. You should give enough of an answer for the person to be able to follow the rest of the talk. If there's something that needs to be handled at great depth, then leave it to question time at the end.

I'll give a demo of how you should do it and then you'll follow. If it seems strange for you to stand up and do the same thing as I've just done, or to follow a student who's followed me, then just pretend you're on the soccer field and you're being asked to practice shooting goals, or you're in music class and you all are being asked to play "Fur Elise" one after another for the teacher. I want to see you shooting goals until I'm sure that when I walk away, you can shoot a goal by yourself. You may have to explain volume.py to multiple groups of people over a long period of time, so learn to enjoy doing it. Just think how many time Mick Jagger has sung "Satisfaction". Assume that you'll be explaining volume.py that many times. You may think that your skill (here coding) is all that counts. It's not till you sell it, that you collect your money.

11. Structured Programming

All imperative languages look much the same. Even if you don't know a language, or its exact syntax, you can look at the code and have a pretty good idea what it's doing. There's a reason for this: all coding is now done in a style called structured programming.

The first programs were written in machine code (lines of 0s and 1s) and then later in assembler (text or mnemonic versions of machine code e.g. add A,B). Program flow (the instruction executed next, if not the next instruction in the program) was controlled by jump instructions. When higher level languages arrived, the jump instruction was implemented by the goto instruction. Control flow by goto lead to unreadable and undebuggable code: code would leap from one page of your program to another and back again. It didn't always go where you expected and it was difficult to write large programs. The control flow problem was fixed by Edsger Dijkstra who building on the work of others wrote the seminar paper

Dijkstra, E. W. (March 1968). "Letters to the editor: go to statement considered harmful". Communications of the ACM 11 (3): 147148. doi:10.1145/362929.362947. ISSN 0001-0782. (EWD215).

Although it took many years, Dijkstra's views were eventually accepted by all, computer languages were rewritten to remove the goto statement, and students (and programming languages) were changed over to structured programming.

Dijkstra realised that not only is programming difficult, but that it's difficult to accurately specify a programming task.

from wikipedia: "Dijkstra was known for his essays on programming; he was the first to make the claim that programming is so inherently difficult and complex that programmers need to harness every trick and abstraction possible in hopes of managing the complexity of it successfully." and "He is famed for coining the popular programming phrase '2 or more, use a for', alluding to the fact that when you find yourself processing more than one instance of a data structure, it is time to encapsulate that logic inside a loop."

As a corollary of Dijkstra's first statement: English is a sufficiently ambiguous or imprecise language that for a large enough program, it will not be possible to unambiguously specify its requirements in English. This accounts for overruns and non-working code in large applications: for examples see Software's Cronic Crisis (start with the Loral disaster). Although computer programmers are mathematically trained and computer code has to be mathematically precise, the customer is usually represented by the people and who are not mathematically trained. As a result, English (rather than mathematics) is used to specify the function of software. Any large software project specified in English is doomed from the start.

The only attempt to make a rigorous language based on English is legalese. Neither programmers nor their customers understand legal English.

Although much effort has gone into mathematically precise specification languages, there are no working examples in the software world. Instead programmers have moved towards writing software, where the functions and interfaces to their code is explicitly stated. This is called Design by Contract (http://en.wikipedia.org/wiki/Design_by_contract) and started with Hoare Logic (http://en.wikipedia.org/wiki/Hoare_logic).

As a result of Dijkstra's work, (from Structured Programming http://en.wikipedia.org/wiki/Structured_programming) accepted control contructs are

  • concatenation: executing a series of instructions (a series of lines of code)
    A = B + C
    print A, B, C
    
  • selection: executing one of several choices e.g.if, then, else, endif (with the implication of only one exit point from this construct, at the bottom of the if..endif.
    if (temperature > 80):
    	print "turning on A/C"
    elif (temperature < 60):
    	print "turning on heater"
    else
    	print "opening windows"
    
    
  • repetition: a statement is executed until the program reaches a certain state or operations are applied to every element of a collection. This is usually expressed with keywords such as while, repeat, for or do..until. Often it is recommended that each loop should only have one entry point (and in the original structural programming, also only one exit point: not all languages enforce this).
    A = 0
    while (A < 10)
    	print A
    	A = A + 1
    
    
  • executing a distant set of elements: e.g. functions from which control may return (from Control flow). (This is not in Dijkstra's original list, but is usually included now.)
    def print_greeting():
    	print "hello world!"
    
    

    print_greeting()
    

Because of the design of modern computer languages, it is difficult, if not impossible, to write unstructured programs. While I'll happily give you examples with syntax errors, and expect you to figure your way out of them, I will not be giving you examples of badly structured programs, or send you on wild goose chases looking for errors in the structure of the code. These may be impossible to write (and have run).

OK, here's an example of a badly constructed piece of structure programming. In poorly designed code, you may have trouble setting up variables before entering a block so that they can be handled without intervention inside the block. Conditions have to be anticipated before entering the block - i.e. you can't handle a condition by exiting the block of code. Structured programming says that you still have to let the code block continue execution. If you don't grasp structured programming, you will not understand that code is required to exit at the end of the block, and you will try to exit in the middle. If you're stymied trying to write a block of conditionals, think whether you're allowing the code to exit at the bottom no matter what happens in the middle.

In structured programming you aren't allowed to do this

set_of_numbers=[1,2,3,4....n]
#non-structured code
for n in set_of_numbers:
	if isprime(n):
		print "found prime number %d", n
		exit
	else:
		print "non-prime number %d", n

#following statements

(In fact python, and many other languages allow you to do this, despite it being bad practice.)

A structured programming way of doing this, which anticipates finding a prime, would be

set_of_numbers=[1,2,3,4..n]
#structured code
n = set_of_numbers.pop()	#remove the top number off the list
while (!isprime(n)):
	print "non-prime number %d", n
	n = set_of_numbers.pop()

print "prime number %d", n

There may be circumstances in which you have to use the non-structured format (I've done it with multipli-nested loops, where you had to break out of an inner loop - it's possible I didn't need to do this), but if you can use the structured format, do so.

Student question: One of the two following pieces of code (modified from temperature_controller.py) is standard structured code; the other shoddy code that's not really structured, but is accepted in many languages (including python). Which piece of code is which and what's the problem?

#!/usr/bin/python

def check_temperature(t):
	if (t > 80):
		result = 1
	elif (t <= 60):
		result = -1
	else:
		result = 0 

	return result

#main
temp = 61
print "the temperature is %d." % temp,
print check_temperature(temp)
#---------------------

#!/usr/bin/python

def check_temperature(t):
	if (t > 80):
		return 1
	elif (t <= 60):
		return -1
	else:
		return 0

#main
temp = 61
print "the temperature is %d." % temp,
print check_temperature(temp)

#---------------------
[68]

How will the non-structured code get you into hot water? Maintenance. If later you come back and don't spend the time to find all the exit points, you might add this line at the end of the if/else block.

print "the temperature is %d." %t

In the non-structured code, it won't run. Do you want to be flying in a plane, whose guidance system has been written in non-structured code? As well code checking programs (which are used in a big project), will see in main() that check_temperature() returns something printable, but that the definition of check_temperature() doesn't.

12. Recap

You now know

  • 4 primitive data types
  • conditional evaluation
  • iteration
  • functions
  • structured programming

These are all the programming tools that were available till the general acceptance of object oriented programming (needed for very large programs) in the 1990s.

You can do a lot of programming with what you have now. Sure there's the arcane syntax associated with each language, which you'll pick up in time, but you have the logical basics. At this point you could in principle go off and teach yourself all the non-object oriented languages. What you need now is practice at putting problems into a format, which can use these tools. (This includes learning standard algorithms.)

Footnotes:


[1]

print "On my next birthday I will be " + repr(age + 1) + " years old"

[2]

print "In 2050 I will be " + repr(age + 2050 - 2008) + " years old"

[3]

The usual (and sloppy) answer from a programmer will be "nothing" (meaning the conditional doesn't branch), which everyone will understand, but will be confusing to a new person. A more correct answer is that the program will continue executing without branching.

[4]

turns on the heater

[5]

opens the windows

[6]

#!/usr/bin/python

temp = 85

if (temp > 80):
	print "the temperature is " + repr(temp) + ". Am turning on the airconditioner."

#--temperature_controller.py-------------------

[7]

#!/usr/bin/python

temp = 75

if (temp > 80):
	print "the temperature is " + repr(temp) + ". Am turning on the airconditioner."
else:
	print "no action taken."

#--temperature_controller.py-------------------

[8]

#!/usr/bin/python

temp = 60

if (temp > 80):
	print "the temperature is " + repr(temp) + ". Am turning on the airconditioner"
elif (temp <= 60):
	print "the temperature is " + repr(temp) + ". Am turning on the heater."
else:
	print "no action taken"

#--temperature_controller.py-------------------

[9]

#!/usr/bin/python

temp = 61

if (temp > 80):
	print "the temperature is " + repr(temp) + ". Am turning on the airconditioner"
elif (temp <= 60):
	print "the temperature is " + repr(temp) + ". Am turning on the heater."
else:
	print "the temperature is " + repr(temp) + ". Am opening the windows."

#--temperature_controller.py-------------------

[10]

#!/usr/bin/python

temp = 61

print "the temperature is", temp,

if (temp > 80):
	print ". Am turning on the airconditioner"
elif (temp <= 60):
	print ". Am turning on the heater."
else:
	print ". Am opening the windows."

#--temperature_controller.py-------------------

[11]

#!/usr/bin/python

temp = 61

print "the temperature is %d." % temp,

if (temp > 80):
	print "Am turning on the airconditioner"
elif (temp <= 60):
	print "Am turning on the heater."
else:
	print "Am opening the windows."

#--temperature_controller.py-------------------

[12]

all of them. (OK so this was review material, not an exam question.)

[13]

You (and the rest of the world) won't be able to debug, fix or maintain it. Don't write (very) smart or obscure code.

[14]

For full marks, you need the standard documentation.

You need to test the code for speed<lower_set_speed, speed>upper_set_speed, speed==lower_set_speed, speed==upper_set_speed and lower_set_speed<upper_set_speed (total of 5 speeds tested).

#! /usr/bin/python
# cruise_control.py

# Jack Brabham (C) 2009, brabham@repco.com
# License GPL v3

# purpose of code:
# maintain car's speed
# if car's speed above upper_set_speed, then brake
# if car's speed below lower_set_speed, then accelerate
# else do nothing

# -------------

# initialisation
upper_set_speed = 65
lower_set_speed = 60
speed = 63

#--------------

#main()

print "speed: ", speed

if (speed < lower_set_speed):
	speed = lower_set_speed
	print "accelerating: new speed", speed

elif (speed > upper_set_speed):
	speed = upper_set_speed
	print "braking: new speed", speed

else:
	print "maintaining speed:", speed

# cruise_control.py ----------------------

[15]

The numbers are output one at a time followed by a carriage return, to give a column of numbers.

[16]

#!/usr/bin/python

sum = 0

for x in [0,1,2,3,4,5]:
	print x,
	sum = sum + x

print   #needed if next line is to be on a new line"
print "the sum is", sum

#--iteration_1.py----------------

[17]

#!/usr/bin/python

sum = 0

for x in [0,1,2,3,4,5]:
	print x*x,
	sum = sum + x*x

print   #needed if next line is to be on a new line"
print "the sum is", sum

#--iteration_2.py----------------

[18]

#!/usr/bin/python

sum = 0

for x in [0,1,2,3,4,5]:
	square = x*x
	print square,
	sum = sum + square

print   #needed if next line is to be on a new line"
print "the sum is", sum

#--iteration_2.py----------------

[19]

#!/usr/bin/python

sum = 0

for x in [0,1,2,3,4,5]:
	square = x*x
	print square,
	sum += square

print   #needed if next line is to be on a new line"
print "the sum is", sum

#--iteration_2.py----------------

[20]

#!/usr/bin/python

sum = 0

for x in range(6):
	square = x*x
	print square,
	sum += square

print   #needed if next line is to be on a new line"
print "the sum is", sum

#--iteration_3.py----------------

[21]

#!/usr/bin/python

sum = 0

for x in range(0,100,7):
	square = x*x
	print square,
	sum += square

print   #needed if next line is to be on a new line"
print "the sum is", sum

#--iteration_4.py----------------

[22]

10

[23]

11

[24]

n

[25]

n+1

[26]

  • for loops can only be used when the number of iterations is known before entering the loop.
  • while loops have to be used when the number of iterations is not known on entering the loop.
  • You would normally use a for for this.

    #! /usr/bin/python
    # sum_numbers.py
    
    # Pythagoras (C) 550BC py@samos.net
    # License GPL v3
    
    # purpose: sums the numbers lower_limit..upper_limit using a for loop
    
    #------------------------
    
    #initialisation
    
    lower_limit = 1
    upper_limit = 10
    step = 1
    sum = 0
    
    #------------------------
    #main()
    
    for x in range(lower_limit,upper_limit+step, step):
    	sum += x
    
    print "the sum of the numbers from %d to %d is %d" %(lower_limit, upper_limit, sum)
    
    # sum_numbers.py -------
    
  • #! /usr/bin/python
    # sum_numbers_2.py
    
    # Pythagoras (C) 550BC py@samos.net
    # License GPL v3
    
    # purpose: sums the numbers lower_limit..upper_limit using a for loop and a while loop
    
    #------------------------
    
    #initialisation
    
    lower_limit = 1
    upper_limit = 10
    step = 1
    sum = 0
    
    #------------------------
    #main()
    
    #for loop
    for x in range(lower_limit,upper_limit+step, step):
    	sum += x
    
    print "the sum of the numbers from %d to %d is %d" %(lower_limit, upper_limit, sum)
    
    #while loop
    
    sum = 0	#it will have a non zero value after exiting the for loop 
    number = lower_limit
    while (number < upper_limit+step):
    	sum += number
    	number += 1
    
    print "the sum of the numbers from %d to %d is %d" %(lower_limit, upper_limit, sum)
    # sum_numbers_2.py -------
    
  • #! /usr/bin/python
    # cruise_control_2.py
    
    # Jack Brabham (C) 2009, brabham@repco.com
    # License GPL v3
    
    # purpose of code:
    # maintain car's speed
    # if car's speed above upper_set_speed, then brake
    # if car's speed below lower_set_speed, then accelerate
    # else do nothing
    
    # -------------
    
    # initialisation
    upper_set_speed = 65
    lower_set_speed = 60
    adjust = 1			# the change caused by braking or accelerating 
    				
    variability_lower_limit = -2	# the upper and lower limits that the speed will randomly change 
    variability_upper_limit = 2	# while the car is maintaining speed
    
    cruise_control_on = 1
    speed = 55
    
    #modules
    import random,time
    
    #--------------
    
    #main()
    
    print "speed: ", speed
    
    while (cruise_control_on):
    	if (speed < lower_set_speed):
    		speed += adjust
    		print "accelerating: new speed", speed
    
    	elif (speed > upper_set_speed):
    		speed -= adjust
    		print "braking:      new speed", speed
    
    	else:
    		# if speed is within the set limits, make a random change of speed
    		# to simulate hills, wind etc
    		speed += random.randint(variability_lower_limit,variability_upper_limit)
    		print "maintaining speed:     ", speed
    
    	time.sleep(1)
    	#cruise_control_on = check_cruise_control_status()
    
    
    # cruise_control_2.py ----------------------
    

    problem: if cruise_control_on is set to false, then the while loop will exit. If cruise_control_on is set back to true, the code will never know.

[27]

#! /usr/bin/python
""" greeting.py
    greeting without any functions
"""

#---------
#main()

print "hello, this code was written by Kirby"

# greeting.py ---------------------------------

[28]

resp is declared in main(). It will be visible in main() and in print_greeting().

[29]

user_name is declared in print_greeting() and will only be visible in print_greeting().

[30]

# ./greeting_4.py
What is your name? Kirby
hello Kirby, nice to meet you
print_greeting: the value of resp is Kirby
Traceback (most recent call last):
  File "./greeting_4.py", line 18, in ?
    print "main: the value of user_name is " + user_name
NameError: name 'user_name' is not defined
  • main() sees resp and outputs its value, but doesn't know about user_name and the program crashes.
  • print_greeting() sees both resp, user_name and outputs their values.

[31]

No, it's a string. You typed your answer on the keyboard. Everything from the keyboard is a string of characters.

[32]

>>> float("1.0")
1.0
>>> float(1.0)
1.0
.

[33]

  • here's how you'd fix the problem in the calling code:

    #get input
    resp = raw_input ("What is the radius of your sphere? ")
    resp=float(resp)
    
    Note
    One minute resp represents a string and the next minute represents a real. A strongly typed language won't allow you to do this. In a strongly typed language you would have to use different names for the response as a string and the response as a real.

    If you fix the problem in the calling code, the function, when called by another piece of code, will still crash when sent a string.

  • Here's how you'd fix problem in the function

    def volume_sphere(r):
    	r=float(r)	
    	pi=3.14159
    
    Note
    As for resp above, r one moment is a string and is then a real. You can only do this in a weakly typed language.

    If you fix the problem in the function, then it can be called both by code which passes a string and by code which passes a number.

For the exercise, either will work. Since you're writing a function here and the only purpose of the calling code is to demonstrate the function, it would be better to fix the function so that it doesn't crash when fed a string.

[34] (4/3)*3 is 4 (by hand). This is not 3 (from the program).

All those who said the answer from the program was OK: your rocket just exploded on the launch pad.

[35]

The function won't work in its new location. Since functions are meant to be self contained, put the import math statement at the beginning of the function. If you have 100 functions all using math.pi, then you'll import math for each of those 100 functions. If you do have 100 functions that use math.pi then you'll likely make a module (which is moved around as self contained unit) out of these 100 functions. The module will have only 1 import math statement.

[36]

Surprisingly a lot of code is written with the wrong answer. These people say that you want to know if a certain piece of code has finished, so you want the notice at the end. If a piece of code has finished quite happily and now the next piece of code is executing, you don't care about it and you don't need to know that it finished. It's only code or hardware that isn't working that you have to do something about. If you come back to your screen and see "housekeeping" displayed and nothing changing for a long time, what do you conclude? If you write the code so that the notice is displayed before the code executes, then you'll know that some call in housekeeping() has hung and you start looking there. If your notice is at the end of the function, then you'll know that the next function has hung. Depending on the complexity of the code, any number of functions could be called next and it may take a while to find the piece of code to check (particularly if it's been written by someone who puts their notices at the end of a function).

Put your notices at the start of the code, before any code that could possibly crash or hang.

[37]

#! /usr/bin/python
# cruise_control_3.py

# Jack Brabham (C) 2009, brabham#repco.com
# License GPL v3

# purpose of code:
# maintain car's speed
# if car's speed above upper_set_speed, then brake
# if car's speed below lower_set_speed, then accelerate
# else do nothing

# -------------

# initialisation
upper_set_speed = 65
lower_set_speed = 60
adjust = 1			# the change caused by braking or accelerating 
				
variability_lower_limit = -2	# the upper and lower limits that the speed will randomly change 
variability_upper_limit = 2	# while the car is maintaining speed

speed = 70

#modules
import random,time

#--------------
#functions

def braking(my_speed):
	my_speed -= adjust	#adjust is global, so you can read from it, but not write to it.
	print "braking:      new speed", my_speed
	return my_speed
	
# braking()--------------

def accelerating(my_speed):
	my_speed += adjust
	print "accelerating: new speed", my_speed
	return my_speed

# accelerating()--------------

def maintaining_speed(my_speed):
	# if speed is within the set limits, make a random change of speed
	# to simulate hills, wind etc
	my_speed += random.randint(variability_lower_limit,variability_upper_limit)
	print "maintaining speed:     ", my_speed
	return my_speed
	

# maintaining_speed()--------------

def check_speed(local_speed):
	#determines which action should be taken as a function of local_speed
	if (local_speed < lower_set_speed):
		local_speed = accelerating(local_speed)

	elif (local_speed > upper_set_speed):
		local_speed = braking(local_speed)

	else:
		local_speed = maintaining_speed(local_speed)

	return local_speed

# check_speed()--------------

def housekeeping():
	#dummy code to simulate all the other computer controlled activities in the car
	print "housekeeping"
	time.sleep(1)

# housekeeping()--------------

def check_cruise_control_status():
	#tells calling program whether cruise control is on.
	#write code here to activate light indicating that cruise control is on.
	cruise_control_on = 1
	return cruise_control_on 

# check_cruise_control_status()--------------

#main()

print "speed: ", speed

cruise_control_on = check_cruise_control_status()
while (1):
	if (cruise_control_on):
		speed = check_speed(speed)
	
	cruise_control_on = check_cruise_control_status()
	housekeeping()

# cruise_control_3.py ----------------------

[38]

move the py file to some other directory (or rename it to foo.py) and then rerun the code that calls the module.

[39]

move or rename the pyc when call_volume.py will stop working.

[40]

functions are guaranteed to be self contained. Programmers swipe code from anywhere they can get it. If someone wants Homer Simpson's well tested volume_sphere() for their next rocket and you just moved the import math statement to the global namespace area, then it's just possible that the function will be moved somewhere that has an import math statement, and the next 4 rockets will work fine. But one day, in a clean up, the import math will be moved out of global namespace (where it shouldn't be). Maybe the tests will catch it or maybe they wont: after all Homer's code worked for the last 4 launches, we don't have to test that code again do we? The 5th rocket blows up. So who's to blame: the guy who last moved import math out of global namespace or you who moved it out of the function volume_sphere() in the first place. Don't rely on others to catch your mistakes. You only have to look around you to see how non-functional half the world is, to know that they won't.

[41]

The hexagon is smaller than the square, so the area must be less than 1.

[42]

The smaller square is inside the hexagon. A>0.5.

[43]

A=pi/4

[44]

The area of the hexagon will be slightly smaller than 0.78.

[45]

#! /usr/bin/python
""" volume_hexagonal_prism.py
    returns volume of hexagonal_prism
"""

#---------------
#functions
def volume_hexagonal_prism(d,h):
	"""
	volume_hexagonal_prism, v1.0 Feb 2008
	Binky binky@gmail.com (C) 2008
	License: GPL v3
	parameters: d (diameter across base, vertex-to-vertex)
	          : h height
	returns: volume
	"""

	import math
	d=float(d)      #allow function to accept both strings and numbers
	h=float(h)      #allow function to accept both strings and numbers
	area = (3*3**(1.0/2.0)/8.0)*d**2
	volume = area * h
	result=volume
	return result
#volume_hexagonal_prism

#---------------
#main

#get input
diameter = raw_input ("What is the vertex-to-vertex diameter of the hexagonal base of your hexagonal_prism? ")
height   = raw_input ("What is the height of your hexagonal_prism? ")

#call function
volume=volume_hexagonal_prism(diameter, height)

#formatted output
print "the volume of your hexagonal_prism of diameter " + diameter + " and height " + height + " is " + repr(volume)

# volume_hexagonal_prism.py ---------------------

# ./volume_hexagonal_prism.py
What is the vertex-to-vertex diameter of the hexagonal base of your hexagonal_prism? 1
What is the height of your hexagonal_prism? 1
the volume of your hexagonal_prism of diameter 1 and height 1 is 0.649519052838329

# ./volume_hexagonal_prism.py
What is the vertex-to-vertex diameter of the hexagonal base of your hexagonal_prism? 1
What is the height of your hexagonal_prism? 2
the volume of your hexagonal_prism of diameter 1 and height 2 is 1.299038105676658

# ./volume_hexagonal_prism.py
What is the vertex-to-vertex diameter of the hexagonal base of your hexagonal_prism? 2
What is the height of your hexagonal_prism? 1
the volume of your hexagonal_prism of diameter 2 and height 1 is 2.598076211353316

[46]

In main(), diameter and height are strings, while volume is a real. (Inside the function volume_hexagonal_prism() diameter and height are converted to reals before calculating the volume.)

[47]

#! /usr/bin/python
#---------------
#functions
def volume_sphere(r):
	"""name      - volume_sphere(r)
	   version   - 1.0 Feb 2008
	   method    - volume =(4.0/3.0)*pi*radius^3
	   parameter - radius as string, integer or real, valid all +/-ve numbers
	   returns   - volume of sphere, float
	   Author    - Homer Simpson, Homer@simpson.com (C) 2008
	   License   - GPL v3.
	"""

	import math     #import the whole math module (including lots of stuff you don't need)
	r=float(r)      #convert string input to float.
			#this allows the function to accept either a string or a number

			#pi in module math is called math.pi
	result=(4.0/3.0)*math.pi*r*r*r
	return result
#volume_sphere

def test_volume_sphere():
	print "self tests for function volume_sphere()"

	#get input
	#resp = raw_input ("What is the radius of your sphere? ")

	#call the function, passing a string
	#volume=volume_sphere(resp)

	#formatted output
	#print "the volume of your sphere is %f" % volume

	#make a few calls to volume_sphere() to test it
	print "the volume of a sphere of radius  1 is " + repr(volume_sphere(1))
	print "the volume of a sphere of radius -1 is " + repr(volume_sphere("-1"))
	print "the volume of a sphere of radius  4 is " + repr(volume_sphere("4"))
	print
#test_volume_sphere

#---------------
def volume_hexagonal_prism(d,h):
	""" volume_hexagonal_prism.py
	    returns volume of hexagonal_prism
	"""
	import math 
	d=float(d)	#allow function to accept both strings and numbers
	h=float(h)	#allow function to accept both strings and numbers
	area = (3*3**(1.0/2.0)/8.0)*d**2
	volume = area * h 
	result=volume
	return result
#volume_hexagonal_prism

def test_volume_hexagonal_prism():
	print "self tests for function volume_hexagonal_prism()"

	#get input
	#diameter = raw_input ("What is the vertex-to-vertex diameter of the hexagonal base of your hexagonal_prism? ")
	#height   = raw_input ("What is the height of your hexagonal_prism? ")

	#call function
	#volume=volume_hexagonal_prism(diameter, height)

	#formatted output
	#print "the volume of your hexagonal_prism of diameter " + diameter + " and height " + height + " is " + repr(volume)

	diameter = 1.0
	height = 1.0
	volume=volume_hexagonal_prism(diameter, height)
	print "the volume of your hexagonal_prism of diameter " + repr(diameter) + " and height " + repr(height) + " is " + repr(volume)

	diameter = 2.0
	height = 1.0
	volume=volume_hexagonal_prism(diameter, height)
	print "the volume of your hexagonal_prism of diameter " + repr(diameter) + " and height " + repr(height) + " is " + repr(volume)

	diameter = 1.0
	height = 2.0
	volume=volume_hexagonal_prism(diameter, height)
	print "the volume of your hexagonal_prism of diameter " + repr(diameter) + " and height " + repr(height) + " is " + repr(volume)

	print
#test_volume_hexagonal_prism
#---------------
#main

print "initialising volume.py"
print

if __name__ == '__main__':
	print "self tests for volume.py"
	print
	test_volume_sphere()
	test_volume_hexagonal_prism()

# volume.py ---------------------

[48]

pip:~# echo "2^-50" | bc -l
.00000000000000088817

This is 10^-15 (approximately).

[49]

4G (4*109)

[50]

the test will be scored as a pass, as a -ve number, no matter how numerically large, will always be less than a small +ve number. You should compare the magnitude of the difference, not the difference (magnitude is the size, i.e. with no -ve sign in front: e.g. the magnitude of -10 is 10).

[51]

#! /usr/bin/python

def compare_results(n_expected, n_found):
	"""
	Indiana Jones (C) 2008
	released under GPL v3
	function: compares the magnitude of the difference of the two parameters with error
			if difference is less than the error, then return "pass"
			else return "fail"
	parameters: real,real: two numbers whose difference will be tested
	returns: string "pass" or "fail"
	"""

	error=10**-15	#difference between two numbers to judge if they are the same number
			#could equally have used error=2**-50

	difference = n_expected - n_found

	if (difference < 0.0):
		difference *= -1.0	#test only the magnitude of the difference
					#could have said: difference = -difference

	if (difference < error):
		result = "pass"
	else:
		result = "fail"

	return result

# compare_results()-----------------------


#main()

#print "%s" compare_results(1.0, 1.000000000000001)
print compare_results(1.0, 1.0000000000000001)
print compare_results(1.0000000000000001, 1.0)
print compare_results(1.0, 1.1)
print compare_results(1.1, 1.0)

# test_compare_results.py-----------------------------

[52]

dennis:# ./test_compare_results.py 
pass
pass
fail
fail

[53]

To make sure the function is testing the size of the magnitude of the difference.

[54]

When you test a test, you show that it passes when it should pass and it fails when it should fail. The first pair of number differ by less than the error (and should pass), while the second pair differ by more than the error (and should fail).

[55]

parameter=float(parameter)

[56]

there is dummy initialisation code, which outputs the string "initialising volume.py" to the screen.

[57]

The trivial answer is that the conditional test fails (this is computer programmer's lingo: to be technically correct, the statement returns false). A complete answer requires an explanation of why the test returns false.

The instructions load the function volume_sphere() from the module volume.py. The interpreter will run the code that's in the global namespace of volume.py, however since we didn't tell python to execute volume.py, the interpreter will not treat the global namespace of volume.py as main(). This will cause the conditional statement to return false.

[58]

>>> print "the volume of a sphere of radius  1 is " + repr(volume_sphere(1)) + " " + compare_results(4.1887902047863905, (volume_sphere(1)))
the volume of a sphere of radius  1 is 4.1887902047863905 pass

[59]

>>> radius=1
>>> expected_result=4.1887902047863905
>>> print "the volume of a sphere of radius " + repr(radius) + " is " + repr(volume_sphere(radius)) + " " + compare_results(expected_result, (volume_sphere(radius)))
the volume of a sphere of radius 1 is 4.1887902047863905 pass

[60]

>>> radius=1
>>> expected_result=4.1887902047863905
>>> volume_found=volume_sphere(radius)
>>> print "the volume of a sphere of radius " + repr(radius) + " is " + repr(volume_found) + " " + compare_results(expected_result, volume_found)
the volume of a sphere of radius 1 is 4.1887902047863905 pass

[61]

copy only the function compare_results() into volume.py. Being a function, it should go somewhere in the top section of the file (before global namespace code).

[62]

We're changing the test code in volume.py. The mockup is changes to test_volume_sphere(). The original code was

print "the volume of a sphere of radius  1 is " + repr(volume_sphere(1))

This uses constants in expressions, and doesn't give an indication whether the result is correct. The new code is

radius=1
expected_result=4.1887902047863905
volume_found=volume_sphere(radius)
print "the volume of a sphere of radius " + repr(radius) + " is " + repr(volume_found) + " " + compare_results(expected_result, volume_found)

Note the call to compare_results() which will output pass/fail.

[63]

#! /usr/bin/python
#---------------
#functions
def volume_sphere(r):
	"""name      - volume_sphere(r)
	   version   - 1.0 Feb 2008
	   method    - volume =(4.0/3.0)*pi*radius^3
	   parameter - radius as string, integer or real, valid all +/-ve numbers
	   returns   - volume of sphere, float
	   Author    - Homer Simpson, Homer@simpson.com (C) 2008
	   License   - GPL v3.
	"""

	import math     #import the whole math module (including lots of stuff you don't need)
	r=float(r)      #convert string input to float.
			#this allows the function to accept either a string or a number

			#pi in module math is called math.pi
	result=(4.0/3.0)*math.pi*r*r*r
	return result

# volume_sphere()----------------------------------------

def test_volume_sphere():
	print "self tests for function volume_sphere()"

	#get input
	#resp = raw_input ("What is the radius of your sphere? ")

	#call the function, passing a string
	#volume=volume_sphere(resp)

	#formatted output
	#print "the volume of your sphere is %f" % volume

	#make a few calls to volume_sphere() to test it
	radius = 1
	expected_result=4.1887902047863905
	volume_found=volume_sphere(radius)
	print "the volume of a sphere of radius " + repr(radius) + " is " + repr(volume_found) + " " + compare_results(expected_result,volume_found)

	radius = "-1"
	expected_result=-4.1887902047863905
	volume_found=volume_sphere(radius)
	print "the volume of a sphere of radius " + repr(radius) + " is " + repr(volume_found) + " " + compare_results(expected_result,volume_found)

	radius = 4
	expected_result=268.08257310632899
	volume_found=volume_sphere(radius)
	print "the volume of a sphere of radius " + repr(radius) + " is " + repr(volume_found) + " " + compare_results(expected_result,volume_found)
	print

# test_volume_sphere()-------------------------------------

def volume_hexagonal_prism(d,h):
	""" volume_hexagonal_prism.py
	    returns volume of hexagonal_prism
	"""
	import math 
	d=float(d)	#allow function to accept both strings and numbers
	h=float(h)	#allow function to accept both strings and numbers
	area = (3*3**(1.0/2.0)/8.0)*d**2
	volume = area * h 
	result=volume
	return result

# volume_hexagonal_prism()---------------------------------


def test_volume_hexagonal_prism():
	print "self tests for function volume_hexagonal_prism()"

	#get input
	#diameter = raw_input ("What is the vertex-to-vertex diameter of the hexagonal base of your hexagonal_prism? ")
	#height   = raw_input ("What is the height of your hexagonal_prism? ")

	#call function
	#volume=volume_hexagonal_prism(diameter, height)

	#formatted output
	#print "the volume of the hexagonal_prism of diameter " + diameter + " and height " + height + " is " + repr(volume)

	diameter = 1.0
	height = 1.0
	expected_result=0.649519052838329
	volume=volume_hexagonal_prism(diameter, height)
	print "the volume of the hexagonal_prism of diameter " + repr(diameter) + " and height " + repr(height) + " is " + repr(volume) + " " + compare_results(expected_result, volume)

	diameter = 2.0
	height = 1.0
	expected_result=2.598076211353316
	volume=volume_hexagonal_prism(diameter, height)
	print "the volume of the hexagonal_prism of diameter " + repr(diameter) + " and height " + repr(height) + " is " + repr(volume) + " " + compare_results(expected_result, volume)

	diameter = 1.0
	height = 2.0
	expected_result=1.299038105676658
	volume=volume_hexagonal_prism(diameter, height)
	print "the volume of the hexagonal_prism of diameter " + repr(diameter) + " and height " + repr(height) + " is " + repr(volume) + " " + compare_results(expected_result, volume)

	print
# test_volume_hexagonal_prism()-----------------------------------------------

def compare_results(n_expected, n_found):
	"""
	Indiana Jones (C) 2008
	released under GPL v3
	function: compares the magnitude of the difference of the two parameters with error
			if difference is less than the error, then return "pass"
			else return "fail"
	parameters: real,real: two numbers whose difference will be tested
	returns: string "pass" or "fail"
	"""

	error=10**-15   #difference between two numbers to judge if they are the same number
	difference=n_expected - n_found

	if (difference < 0.0):
		difference *= -1.0      #test only the magnitude of the difference

	if (difference < error):
		result = "pass"
	else:
		result = "fail"

	return result

# compare_results()----------------------------------

#main

print "initialising volume.py"
print

if __name__ == '__main__':
	print "self tests for volume.py"
	print
	test_volume_sphere()
	test_volume_hexagonal_prism()

# volume_2.py ---------------------

[64]

dennis:# ./volume.py
initialising volume.py

self tests for volume.py

self tests for function volume_sphere()
the volume of a sphere of radius 1 is 4.1887902047863905 pass
the volume of a sphere of radius '-1' is -4.1887902047863905 pass
the volume of a sphere of radius 4 is 268.08257310632899 pass

self tests for function volume_hexagonal_prism()
the volume of the hexagonal_prism of diameter 1.0 and height 1.0 is 0.649519052838329 pass
the volume of the hexagonal_prism of diameter 2.0 and height 1.0 is 2.598076211353316 pass
the volume of the hexagonal_prism of diameter 1.0 and height 2.0 is 1.299038105676658 pass

[65]

#! /usr/bin/python
#---------------
#functions
def volume_sphere(r):
	"""name      - volume_sphere(r)
	   version   - 1.0 Feb 2008
	   method    - volume =(4.0/3.0)*pi*radius^3
	   parameter - radius as string, integer or real, valid all +/-ve numbers
	   returns   - volume of sphere, float
	   Author    - Homer Simpson, Homer@simpson.com (C) 2008
	   License   - GPL v3.
	"""

	import math     #import the whole math module (including lots of stuff you don't need)
	r=float(r)      #convert string input to float.
			#this allows the function to accept either a string or a number

			#pi in module math is called math.pi
	result=(4.0/3.0)*math.pi*r*r*r
	return result

# volume_sphere()----------------------------------------

def test_volume_sphere():
	print "self tests for function volume_sphere()"

	loop_list=[]

	radius = 1
	expected_result=4.1887902047863905
	volume=volume_sphere(radius)
	parameter_list=[radius,expected_result,volume]
	loop_list.append(parameter_list)

	radius = "-1"
	expected_result=-4.1887902047863905
	volume=volume_sphere(radius)
	parameter_list=[radius,expected_result,volume]
	loop_list.append(parameter_list)

	radius = 4
	expected_result=268.08257310632899
	volume=volume_sphere(radius)
	parameter_list=[radius,expected_result,volume]
	loop_list.append(parameter_list)

	for parameter_list in loop_list:
		volume=parameter_list.pop()	#last entry popped first
		expected_result=parameter_list.pop()
		radius=parameter_list.pop()
		print "the volume of a sphere of radius " + repr(radius) + " is " + repr(volume) + " " + compare_results(expected_result,volume)

	print

# test_volume_sphere()-------------------------------------

def volume_hexagonal_prism(d,h):
	""" volume_hexagonal_prism.py
	    returns volume of hexagonal_prism
	"""
	import math 
	d=float(d)	#allow function to accept both strings and numbers
	h=float(h)	#allow function to accept both strings and numbers
	area = (3*3**(1.0/2.0)/8.0)*d**2
	volume = area * h 
	result=volume
	return result

# volume_hexagonal_prism()---------------------------------


def test_volume_hexagonal_prism():
	print "self tests for function volume_hexagonal_prism()"

	loop_list = []

	diameter = 1.0
	height = 1.0
	expected_result=0.649519052838329
	volume=volume_hexagonal_prism(diameter, height)
	parameter_list = [diameter, height, expected_result, volume]
	loop_list.append(parameter_list)

	diameter = 2.0
	height = 1.0
	expected_result=2.598076211353316
	volume=volume_hexagonal_prism(diameter, height)
	parameter_list = [diameter, height, expected_result, volume]
	loop_list.append(parameter_list)

	diameter = 1.0
	height = 2.0
	expected_result=1.299038105676658
	volume=volume_hexagonal_prism(diameter, height)
	parameter_list = [diameter, height, expected_result, volume]
	loop_list.append(parameter_list)

	for parameter_list in loop_list:
		volume=parameter_list.pop()	#last entry popped first
		expected_result=parameter_list.pop()
		height=parameter_list.pop()
		diameter=parameter_list.pop()
		print "the volume of the hexagonal_prism of diameter " + repr(diameter) + " and height " + repr(height) + " is " + repr(volume) + " " + compare_results(expected_result, volume)

	print
# test_volume_hexagonal_prism()-----------------------------------------------

def compare_results(n_expected, n_found):
	"""
	Indiana Jones (C) 2008
	released under GPL v3
	function: compares the magnitude of the difference of the two parameters with error
			if difference is less than the error, then return "pass"
			else return "fail"
	parameters: real,real: two numbers whose difference will be tested
	returns: string "pass" or "fail"
	"""

	error=10**-15   #difference between two numbers to judge if they are the same number
	difference=n_expected - n_found

	if (difference < 0.0):
		difference *= -1.0      #test only the magnitude of the difference

	if (difference < error):
		result = "pass"
	else:
		result = "fail"

	return result

# compare_results()----------------------------------

#main

print "initialising volume.py"
print

if __name__ == '__main__':
	print "self tests for volume.py"
	print
	test_volume_sphere()
	test_volume_hexagonal_prism()

# volume_3.py ---------------------

[66]

Which code would run on a chip in the engine bay listening to sensors and activating actuators, and which code would run on a chip in the dashboard listening to controls/switches on the dashboard and turn on a light on the dashboard indicating that cruise control was on.

[67]

#! /usr/bin/python
# run_cruisecontrol.py

# Jack Brabham (C) 2009, brabham#repco.com
# License GPL v3

# purpose of code:
# to activate and display state of cruisecontrol

# -------------

# initialisation

speed = 70

#modules
import time,cruisecontrol

#--------------
#functions

def housekeeping():
	#dummy code to simulate all the other computer controlled activities in the car
	print "housekeeping"
	time.sleep(1)

# housekeeping()--------------

def check_cruise_control_status():
	#tells calling program whether cruise control is on.
	#write code here to activate light indicating that cruise control is on.

	cruise_control_on = 1
	return cruise_control_on 

# check_cruise_control_status()--------------

#main()

print "speed: ", speed
cruise_control_on = check_cruise_control_status()

while (1):
	if (cruise_control_on):
		speed = cruisecontrol.check_speed(speed)

	cruise_control_on = check_cruise_control_status()
	housekeeping()

# run_cruisecontrol.py ----------------------

#! /usr/bin/python
# cruisecontrol.py

# Jack Brabham (C) 2009, brabham#repco.com
# License GPL v3

# purpose of code:
# maintain car's speed
# if car's speed above upper_set_speed, then brake
# if car's speed below lower_set_speed, then accelerate
# else do nothing

# -------------

# initialisation
upper_set_speed = 65
lower_set_speed = 60
adjust = 1			# the change caused by braking or accelerating 
				
variability_lower_limit = -2	# the upper and lower limits that the speed will randomly change 
variability_upper_limit = 2	# while the car is maintaining speed

#modules
import random

#--------------
#functions

def braking(my_speed):
	my_speed -= adjust	#adjust is global, so you can read from it, but not write to it.
	print "braking:      new speed", my_speed
	return my_speed
	
# braking()--------------

def accelerating(my_speed):
	my_speed += adjust
	print "accelerating: new speed", my_speed
	return my_speed

# accelerating()--------------

def maintaining_speed(my_speed):
	# if speed is within the set limits, make a random change of speed
	# to simulate hills, wind etc
	my_speed += random.randint(variability_lower_limit,variability_upper_limit)
	print "maintaining speed:     ", my_speed
	return my_speed
	

# maintaining_speed()--------------

def check_speed(local_speed):
	#determines which action should be taken as a function of local_speed
	if (local_speed < lower_set_speed):
		local_speed = accelerating(local_speed)

	elif (local_speed > upper_set_speed):
		local_speed = braking(local_speed)

	else:
		local_speed = maintaining_speed(local_speed)

	return local_speed

# check_speed()--------------

# cruisecontrol.py ----------------------

[68]

The problem in the 2nd lot of code is that there are multiple exit points from the if/else block.

AustinTek homepage | Linux Virtual Server Links | AZ_PROJ map server |