AustinTek homepage | | Linux Virtual Server Links | AZ_PROJ map server | |
Copyright © 2008,2009 Joseph Mack
v20090511, released under GPL-v3.
Table of Contents
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.
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 |
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 |
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
navigating
It's not a lot to ask.
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
From the NCSA discussion mailing list (and other places) comes these suggestions for windows/Mac editors other than vi and emacs
Mac:
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++ (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. |
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.
pick a name for your python files directory e.g. class_files.
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.
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!" |
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! |
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 |
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
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
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 print lines, the variables were output, along with some text for the user.
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. |
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 elseIf instead you do
if (x=0) do something else do something elsethen 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. |
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
Here's my code [6] .
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] .
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] .
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] .
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 |
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.
#!/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---------------- |
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] .
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).
Write code that does the following
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. |
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. |
answer: [14]
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.
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
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.
|
Change the code in iteration_1.py to
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
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. |
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
|
Modify iteration_2.py to use this new syntax. Here's my new version [19] .
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 |
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.
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 sum of the numbers from lower_limit to upper_limit is sum |
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. |
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
In python you have to do this instead
or this
To be safe you'd have to do this
|
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. |
#cruise_control_on = check_cruise_control_status() |
answers: [26]
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". |
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]
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.
Note | |
---|---|
nomenclature: main()calls the function. When the function has finished executing, it returns (execution returns to the line following the call). |
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
If your code is nested to great depth (not a great idea), deeply nested code will be diplaced to the right across the page.
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.
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).
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
Scope is the range in which a variable can be referenced. In programming languages,
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.)
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
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
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 |
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.
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?
(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 |
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.
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.
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.
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
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.
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,
procedure/subroutine/functions have the following properties
Note | |
---|---|
End Lesson 11 |
Copy cruise_control_2.py to cruise_control_3.py.
#write code here to activate light indicating that cruise control is on. |
answer: [37]
Let's say your function is one or both of
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.
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.
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):
|
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.)
By now I hope you realise
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 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 |
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
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 ???.
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
|
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 volume of a hexagonal prism of diameter xxxx and height xxxx is xxxxx |
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.
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 |
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
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 |
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
|
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
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? |
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] ?)
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
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.
Now do the merge
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 |
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
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. |
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.
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]
You have now written a module (volume.py) that is
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
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.
When writing code, make sure in your mind that you separate what the code does from how you implement 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.
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 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.
There a couple of rules
"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:
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.
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
A = B + C print A, B, C |
if (temperature > 80): print "turning on A/C" elif (temperature < 60): print "turning on heater" else print "opening windows" |
A = 0 while (A < 10) print A A = A + 1 |
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) #--------------------- |
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.
You now know
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.)
[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]
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 |
[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 | |