Py Tutorial 113 139
Py Tutorial 113 139
lines.append(line)
line = input(’Next line: ’)
# ....
It is useful to start by thinking of the objects needed, and give them names.
• A Polygon is needed. Call it poly.
• A list of vertices is needed. Call it vertices. I need to append to this list. It must be initialized
first.
• The latest mouse click point is needed. Call it pt.
Certainly the overall process will be repetitious, choosing point after point. Still it may not be at all clear
how to make an effective Python loop. In challenging situations like this it is often useful to imagine a
concrete situation with a limited number of steps, so each step can be written in sequence without worrying
about a loop.
For instance to get up to a triangle (3 vertices in our list and a fourth mouse click for the sentinel),
you might imagine the following sequence, undrawing each old polygon before the next is displayed with the
latest mouse click included:
rect.setOutline("red")
rect.draw(win)
vertices = list()
pt = win.getMouse()
3.3. WHILE STATEMENTS 114
vertices.append(pt)
poly = Polygon(vertices)
poly.draw(win) # with one point
pt = win.getMouse()
poly.undraw()
vertices.append(pt)
poly = Polygon(vertices)
poly.draw(win) # with two points
pt = win.getMouse()
poly.undraw()
vertices.append(pt)
poly = Polygon(vertices)
poly.draw(win) # with three points
pt = win.getMouse() # assume outside the region
rect.undraw()
return poly
There is a fine point here that I missed the first time. The vertices of a Polygon do not get mutated in this
system. A new Polygon gets created each time with the new vertex list. The old Polygon does not go away
automatically, and extraneous lines appear in the picture if the old polygon is not explicitly undrawn each
time before a new version is redrawn with an extra vertex. The timing for the undraw needs to be after the
next mouse click and presumably before the next Polygon is created, so it could be before or after the line
vertices.append(pt). I arbitrarily chose for it to go before the vertices list is changed . The rest of the
order of the lines is pretty well fixed by the basic logic.
If you think of the repetitions through a large number of loops, the process is essentially circular (as
suggested by the word ’loop’). The body of a loop in Python, however, is written as a linear sequence: one
with a first line and a last line, a beginning and an end. We can cut a circle anywhere to get a piece with
a beginning and an end. In practice, the place you cut the loop for Python has one main constraint. The
continuation condition in the while heading must make sense there. The processing in Python from the end
of one time through the loop to the beginning of the next loop is separated by the test of the condition in
the heading.
It can help to look at a concrete example sequence like the steps listed above for creating a triangle. The
continuation condition is for pt to be in the rectangle, so using the previously written function isInside,
the loop heading will be
while isInside(pt, rect):
With this condition in mind, look for where to split to loop. It needs to be after a new pt is clicked (so it can
be tested) and before the next Polygon is created (so it does not include the sentinel point by mistake). In
particular, with the sequence above, look and see that the split could go before or after the poly.undraw()
line. Exercise 3.3.4.1 below considers the case where the split goes before this line. I will proceed with the
choice of splitting into a Python loop after the undraw line. This makes the loop be
while isInside(pt, rect):
vertices.append(pt)
poly = Polygon(vertices)
poly.draw(win)
pt = win.getMouse()
poly.undraw()
If you follow the total sequence of required steps above for making the concrete triangle, you see that this
full sequence for the loop is only repeated twice. The last time there is no poly.undraw() step. I could redo
the loop moving the undraw line to the top, which caused different issues (Exercise 3.3.4.1 below). Instead
think how to make it work at the end of the final time through the loop.
There are several possible approaches. You want the undraw line every time except for the last time.
Hence it is a statement you want sometimes and not others. That suggests an if statement. The times you
want the undraw are when the loop will repeat again. This is the same as the continuation condition for the
3.3. WHILE STATEMENTS 115
loop, and you have just read the next value for pt! You could just add a condition in front of the last line
of the loop:
if isInside(pt, rect):
poly.undraw()
I find this option unaesthetic: it means duplicating the continuation test twice in every loop.
Instead of avoiding the undraw as you exit the loop, another option in this case is to undo it: just redraw
the polygon one final time beyond the loop. This only needs to be done once, not repeatedly in the loop.
Then the repetitious lines collapse neatly into the loop, leaving a few of lines of the overall sequence before
and after the loop. In the end the entire function is:
def polyHere(rect, win):
’’’ Draw a polygon interactively in Rectangle rect, in GraphWin win.
Collect mouse clicks inside rect into a Polygon.
When a click goes outside rect, stop and return the final polygon.
The polygon ends up drawn. The method draws and undraws rect.’’’
rect.setOutline("red")
rect.draw(win)
vertices = list()
pt = win.getMouse()
while isInside(pt, rect):
vertices.append(pt)
poly = Polygon(vertices)
poly.draw(win)
pt = win.getMouse()
poly.undraw()
5The basic issue is similar to the old version: the undraw is not always needed at the beginning, either. In this place it is
not need the first time through the loop. The two basic approaches considered for the previous version still work here: break
into cases inside the loop or make an extra compensating action outside the loop. Further hint: It is legal to draw a polygon
with an empty vertex list – nothing appears on the screen.
3.3. WHILE STATEMENTS 116
Exercise 3.3.4.2. Write a program very similar to makePoly.py, and call it makePath.py, with a
function pathHere. The only outward difference between polyHere and pathHere is that while the first
creates a closed polygon, and returns it, and the new one creates a polygonal path, without the final point
being automatically connected to the first point, and a list of the lines in the path is returned. Internally
the functions are quite different. The change simplifies some things: no need to undraw anything in the
main loop - just draw the latest segment each time going from the previous point to the just clicked point.
There is a complication however, you do need deal specially with the first point. It has no previous point to
connect to. I suggest you handle this before the main loop, and draw the point so it is a visible guide for
the next point. After your main loop is finished undraw this initial point. (The place on the screen will still
be visible if an initial segment is drawn. If no more points were added, the screen is left blank, which is the
way it should be.) You also need to remember the previous point each time through the main loop.
In your main program, test the makePath function several times. Use the list of lines returned to loop
and change the color in one path and the width of the lines in another path. A portion of a sample image
is shown below after all this is done.
In earlier animation examples a while loop would also have been useful. Rather than continuing the
animation a fixed number of times, it would be nice for the user to indicate by a mouse click when she has
watched long enough. Thus far the only way to use the mouse has been with getMouse(). This is not going
to work in an animation, because the computer stops and waits for a click with getMouse(), whereas the
animation should continue until the click.
In full-fledged graphical systems that respond to events, this is no problem. Zelle’s graphics is built
on top of a capable event-driven system, and in fact, all mouse clicks are registered, even outside calls to
getMouse(), though Zelle’s documentation pages do not mention it.
As an example, run example program randomCirclesWhile.py. Be sure to follow the prompt saying to
click to start and to end.
Aside from the prompts, the difference from the previous randomCircles.py program is the replacement
of the original simple repeat loop heading
for i in range(75):
code for an animation step
by the following initialization and while loop heading:
while win.checkMouse() == None: #NEW
3.3. WHILE STATEMENTS 117
You can look in Idle at the full source code for bounce2.py if you like. The changes from bounce1.py
are all marked with a comment starting with #NEW, and all the major changes have been described above.
In the examples so far of the use of checkMouse(), we have only used the fact that a point was clicked,
not which point. The next example version, bounce3.py, does use the location of mouse clicks that are read
with checkMouse() to change the direction and speed of the ball. Try it.
This version only slightly modifies the central animation function, bounceInBox, but wraps it in another
looping function that makes the direction and speed of the ball change on each mouse click. Hence the
mouse clicks detected in bounceInBox need to be remembered and then returned after the main animation
loop finishes. That requires a name, pt, to be given to the last mouse click, so it can be remembered. This
means modifying the main animation loop to initialize the variable pt before the loop and reset it at the
end of the loop, much as in the use of getMouse() for the interactive polygon creation. That explains the
first three NEW lines and the last two NEW lines in the revised bounceInBox:
def bounceInBox(shape, dx, dy, xLow, xHigh, yLow, yHigh, win):
’’’ Animate a shape moving in jumps (dx, dy), bouncing when
its center reaches the low and high x and y coordinates.
The animation stops when the mouse is clicked, and the
last mouse click is returned.’’’
delay = .001
pt = None #NEW
while pt == None: #NEW
shape.move(dx, dy)
center = shape.getCenter()
x = center.getX()
y = center.getY()
isInside = True #NEW
if x < xLow or x > xHigh:
dx = -dx
isInside = False #NEW
if y < yLow or y > yHigh:
dy = -dy
isInside = False #NEW
time.sleep(delay)
if isInside: # NEW don’t mess with dx, dy when outside
pt = win.checkMouse() #NEW
return pt #NEW
I initially made only the changes discussed so far (not the ones involving the new variable isInside). The
variable isInside was in response to a bug that I will discuss after introducing the simple function that
wraps around bounceInBox:
Each time the mouse is clicked, the ball is to switch direction and move toward the last click, until the
stopping condition occurs, when there is a click above the stop line. This is clearly repetitive and needs a
while loop. The condition is simply to test the y coordinate of the mouse click against the the height of the
stop line. The body of the loop is very short, since we already have the utility function getShift, to figure
out (dx, dy) values.
def moveInBox(shape, stopHeight, xLow, xHigh, yLow, yHigh, win): #NEW
’’’Shape bounces in win so its center stays within the low and high
x and y coordinates, and changes direction based on mouse clicks,
terminating when there is a click above stopHeight.’’’
scale = 0.01
pt = shape.getCenter() # starts motionless
while pt.getY() < stopHeight:
(dx, dy) = getShift(shape.getCenter(), pt)
pt = bounceInBox(shape, dx*scale, dy*scale,
3.3. WHILE STATEMENTS 119
Exercise 3.3.4.4. ** Write a program madlib4.py that modifies the getKeys method of madlib2.py
to use a while loop. (This is not an animation program, but this section is where you have had the most
experience with while loops!) Hint: 6
Exercise 3.3.4.5. ** Write a graphical game program, findHole.py, “Find the Hole”. The program
should use a random number generator to select a point and a perhaps radius around that point. These
determine the target and are not revealed to the player initially. The user is then prompted to click around
on the screen to “find the hidden hole”. You should show the points the user has tried. Once the user selects
a point that is within the chosen radius of the mystery point, the mystery circle should appear, and the point
of the final successful mouse click should show. There should be a message announcing how many steps it
took, and the game should end.
Hint: you have already seen the code to determine the displacement (dx, dy) between two points: use
the getShift function in bounce2.py. Once you have the displacement (dx, dy) between the hidden center
and the latest mouse click, the distance between the points is (dx*dx + dy*dy)**0.5, using the Pythagorean
Theorem of geometry. If this distance is no more than the radius you have chosen for the mystery circle, then
the user has found the circle! You can use getShift as written, or modify it into a function getDistance
that directly returns the distance between two points.
Many elaborations on this game are possible! Have fun with it!
3.3.5. Fancier Animation Loop Logic (Optional). The final variation is the example program
bounce4.py, which has the same outward behavior as bounce3.py, but it illustrates a different internal
design decision. The bounce3.py version has two levels of while loop in two methods, moveInBox.for mouse
clicks and bounceInBox for bouncing. The bounce4.py version puts all the code for changing direction inside
the main animation loop in the old outer function, moveInBox. There are now three reasons to adjust (dx,
dy): bouncing off the sides, bouncing off the top or bottom, or a mouse click. That is a simplification and
unification of the logic in one sense. The complication now is that the logic for determining when to quit
6This is actually the most natural approach. I avoided while loops initially, when only for loops had been discussed. It
is redundant in the original approach, however, to find every instance of ’{’ to count the number of repetitions and then find
them all again when extracting the cue keys. A more natural way to control the loop is a while loop stopping when there are
no further occurrences of ’{’. This involves some further adjustments. You must cut the loop in a different place (to end after
searching for ’{’) . As discussed before, cutting a loop in a different place may require changes before and after the loop, too.
3.4. ARBITRARY TYPES TREATED AS BOOLEAN 120
is buried deep inside the if-else logic, not at the heading of the loop. The test for mouse clicks is inside the
while loop and further inside another if statment. The test of the mouse click may merely lead to a change in
(dx, dy), or is a signal to quit. Here is the revised code, with a discussion afterward of the return statement:
def moveInBox(shape, stopHeight, xLow, xHigh, yLow, yHigh, win):
’’’ Animate a shape moving toward any mouse click below stopHeight and
bouncing when its center reaches the low or high x or y coordinates.
The animation stops when the mouse is clicked at stopHeight or above.’’’
scale = 0.01
delay = .001
dx = 0 #NEW dx and dy no longer parameters
dy = 0 #NEW
while True: #NEW exit loop at return statement
center = shape.getCenter()
x = center.getX()
y = center.getY()
isInside = True
if x < xLow or x > xHigh:
dx = -dx
isInside = False
if y < yLow or y > yHigh:
dy = -dy
isInside = False
if isInside:
pt = win.checkMouse()
if pt != None: #NEW dealing with mouse click now here
if pt.getY() < stopHeight: # switch direction
(dx, dy) = getShift(center, pt)
(dx, dy) = (dx*scale, dy*scale)
else: #NEW exit from depths of the loop
return #NEW
shape.move(dx, dy)
time.sleep(delay)
Recall that a return statement immediately terminates function execution. In this case the function returns
no value, but a bare return is legal to force the exit. Since the testing is not done in the normal while
condition, the while condition is set as permanently True. This is not the most common while loop pattern!
It obscures the loop exit. The choice between the approach of bounce3.py and bounce4.py is a matter of
taste in the given situation.
False
>>> bool(’’)
False
>>> bool(’0’)
True
>>> bool(’False’)
True
>>> bool([])
False
>>> bool([0])
True
The result looks pretty strange, but there is a fairly short general explanation: Almost everything is converted
to True. The only values among built-in types that are interpreted as False are
• The Boolean value False itself
• Any numerical value equal to 0 (0, 0.0 but not 2 or -3.1)
• The special value None
• Any empty sequence or collection, including the empty string(’’, but not ’0’ or ’hi’ or ’False’)
and the empty list ([], but not [1,2, 3] or [0])
A possibly useful consequence occurs in the fairly common situation where something needs to be done with
a list only if it is nonempty. In this case the explicit syntax:
if len(aList) > 0:
doSomethingWith(aList)
can be written with the more succinct python idiom
if aList:
doSomethingWith(aList)
This automatic conversion can also lead to extra trouble! Suppose you prompt the user for the answer to a
yes/no question, and want to accept ’y’ or ’yes’ as indicating True. You might write the following incorrect
code. Read it:
ans = input(’Is this OK? ’)
if ans == ’y’ or ’yes’:
print(’Yes, it is OK’)
The problem is that there are two binary operations here: ==, or. Comparison operations all have higher
precedence than the logical operations or, and. The if condition above can be rewritten equivalently with
parentheses. Read and consider:
(ans == ’y’) or ’yes’
Other programming languages have the advantage of stopping with an error at such an expression, since a
string like ’yes’ is not Boolean. Python, however, accepts the expression, and treats ’yes’ as True! To
test, run the example program boolConfusion.py, shown below:
ans = ’y’
if ans == ’y’ or ’yes’:
print(’y is OK’)
ans = ’no’
if ans == ’y’ or ’yes’:
print(’no is OK!!???’)
Python detects no error. The or expression is always treated as True, since ’yes’ is a non-empty sequence,
interpreted as True.
The intention of the if condition presumably was something like
(ans == ’y’) or (ans == ’yes’)
This version also translates directly to other languages. Another correct Pythonic alternative that groups
the alternate values together is
3.5. FURTHER TOPICS TO CONSIDER 122
Beyond these language features, Python has a vast collection of useful modules. An example program,
bbassign.py, is a real-world program that I have in regular use for processing inconveniently organized files
created by Blackboard for homework submissions. It is a command-line script that uses string methods
and slicing and both kinds of loops, as well is illustrating some useful componets in modules sys, os, and
os.path, for accessing command line parameters, listing file directories, creating directories, and moving and
renaming files.
3.6. Summary
(1) Comparison operators produce a Boolean result (type bool, either True or False): [3.1.4]
If the condition is true, then do the indented statement block. If the condition is not true,
then s
If the condition is true, then do the first indented block only. If the condition is not true, then
skip the first indented block and do the one after the else:.
If the condition is true, then do the first indented block only. If the condition is not true, then
skip the first indented block and do the one after the else:.
(b) if-else statement [3.1.3]
if condition:
indentedStatementBlockForTrueCondition
else:
indentedStatementBlockForFalseCondition
If the condition is true, then do the first indented block only. If the condition is not true, then
skip the first indented block and do the one after the else:.
(c) The most general syntax for an if statement, if-elif-...-else [3.1.5]:
if condition1 :
indentedStatementBlockForTrueCondition1
3.6. SUMMARY 124
elif condition2 :
indentedStatementBlockForFirstTrueCondition2
elif condition3 :
indentedStatementBlockForFirstTrueCondition3
elif condition4 :
indentedStatementBlockForFirstTrueCondition4
else:
indentedStatementBlockForEachConditionFalse
The if, each elif, and the final else line are all aligned. There can be any number of elif
lines, each followed by an indented block. (Three happen to be illustrated above.) With this
construction exactly one of the indented blocks is executed. It is the one corresponding to the
first True condition, or, if all conditions are False, it is the block after the final else line
(d) if-elif [3.1.5]
The else: clause above may also be omitted. In that case, if none of the conditions is true,
no indented block is executed.
(6) while statements [3.3.1]
while condition:
indentedStatementBlock
Do the indented block if condition is True, and at the end of the indented block loop back and
test the condition again, and continue repeating the indented block as long as the condition is True
after completing the indented block. Execution does not stop in the middle of the block, even if
the condition becomes False at that point.
A while loop can be used to set up an (intentionally) apparently infinite loop by making
condition be just True. To end the loop in that case, there can be a test inside the loop that
sometime becomes True, allowing the execution of a return statement to break out of the loop.
[3.3.5]
(7) range function with three parameters [3.3.1]
range(start, pastEnd, step)
Return a list of elements [start, start+step, ...], with each element step from the previousCGI
one, ending just before reaching pastEnd. If step is positive, pastEnd is larger than the last
element. If step is negative, pastEnd is smaller than the last element.
(8) Type tuple
( expression , expression , and so on )
( expression , )
()
(a) A literal tuple, with two or more elements, consists of a comma separated collection of values
all enclosed in parentheses. A literal tuple with only a single element must have a comma after
the element to distinguish from a regular parenthesized expression. [3.2]
(b) A tuple is a kind of sequence.
(c) Tuples, unlike lists, are immutable (may not be altered)..
(9) Additional programming techniques
(a) These techniques extend the techniques listed in the summary of the previous chapter. [2.6]
(b) The basic pattern for programming with a while loop is [3.3.1]
initialization
while continuation condition :
main action to repeat
prepare variables for next loop
(c) Interactive while loops generally follow the pattern [3.3.3]
input first data from user
while continue based on test of user data :
process user data
input next user data
Often the code to input the first data and the later data is the same, but it must appear in
both places!
3.6. SUMMARY 125
This chapter leads up to the creation of dynamic web pages. These pages and supporting programs and
data may be tested locally via a simple Python web server available on your local machine. If you have
access, the pages and programs may be uploaded to a public server accessible to anyone on the Internet.
A few disclaimers:
• This tutorial does not cover uploading to an account on a public server.
• No core Python syntax is introduced in this Chapter. Only a few methods in a couple of Python
library modules are introduced.
• The chapter is by no means a major source of information about HTML code. That is mostly
avoided with the use of a modern word-processor-like HTML editor. As a specific example, the
open source HTML editor Kompozer is discussed.
The chapter does allow you to understand the overall interaction between a browser (like Firefox on your
local machine) and a web server and to create dynamic web content with Python. We treat interaction with
the web basically as a mechanism to get input into a Python program and data back out and displayed.
Web pages displayed in your browser are used for both the input and the output. The advantage of a public
server is that it can also be used to store data accessible to people all over the world.
There are a number of steps in the development in this chapter, so I start with an overview:
(1) A few bits about the basic format of hypertext markup language are useful to start.
(2) The simplest pages to start writing in Kompozer are just static web pages, formatted like a word-
processing document.
(3) Next we look at pages generated dynamically. An easy way to accomplish this is to create specialized
static pages to act as templates into which the dynamic data is easily embedded. Web page creation
can be tested totally locally, by creating HTML files and pointing your web browser to them.
Initially we supply input data by our traditional means (keyboard input or function parameters),
and concentrate on having our Python program convert the input to the desired output, and display
this output in a web page.
(4) We generate data from within a web page, using web forms (generated via Kompozer). Initially we
will test web forms by automatically dumping their raw data.
(5) To fully integrate a browser and server, we use 1) web forms to provide data, 2) a Python program
specified on the server to transform the input data into the desired output, 3) embed the output
in a new dynamic web page that gets sent back to your browser. This Python server program
transforms the input data, and generates output web pages much like we did in step 3.
(6) Finally, if you have an account like Loyola Computer Science students, you can upload and show
off your work on your own personal web site, accessible to everyone on the Internet.
Hypertext markup language (HTML) is very different in that regard. It produces a file of entirely
human-readable characters, that could be produced with a plain text editor.
For instance in HTML, the largest form of a heading with the text “Web Introduction”, would look like
<h1>Web Introduction</h1>
The heading format is indicated by bracketing the heading text ’Web Introduction’ with markup sequences,
<h1> beforehand, and </h1> afterward. All HTML markup is delimited by tags enclosed in angle brackets,
and most tags come in pairs, surrounding the information to be formatted. The end tag has an extra ’/’.
Here ’h’ stands for heading, and the number indicates the relative importance of the heading. (There is also
h2, h3, .... for smaller headings.) In the early days of HTML editing was done in a plain text editor, with
the tags being directly typed in by people who memorized all the codes!
With the enormous explosion of the World Wide Web, specialized software has been developed to make
web editing be much like word processing, with a graphical interface, allowing formatting to be done by
selecting text with a mouse and clicking menus and icons labeled in more natural language. The software
then automatically generates the necessary markup. An example used in these tutorials is the open source
Kompozer, available at http://kompozer.net. (Careful – although this is free, open source software, the
URL is Kompozer.net, not Kompozer.org. There is a site Kompozer.org that is designed to confuse you!)
You can open Kompozer and easily generate a document with a heading, and italic and boldfaced portions....
4.1.2. Introduction to Static Pages in Kompozer. This section introduces the Kompozer web
page editor to create static pages. Kompozer is used because it is free software, and is pretty easy to use, like
a common word processor. Unlike a common word processor you will be able to easily look at the HTML
markup code underneath. It is not necessary to know a lot about the details of the markup codes for HTML
files to use Kompozer.
We will use static pages later as a part of making dynamic pages, using the static pages as templates in
which we insert data dynamically.
To creating static web pages
(1) If you are in a Loyola University Windows lab, go to the start menu -> Loyola software -> Internet
-> Kompozer. (It may be under Math and Comp Sci instead.) You may get pop-up window
wanting to count users of Kompozer. Click OK as another user of Kompozer.
(2) However you start Kompozer, go to the File menu and click on New. You will get what looks like
an empty document.
(3) Look at the bottom of your window. You should see a ’Normal’ tabs selected, with other choices
beside it, including a Source tab. Click on the Source tab. You should see that, though you have
added no content, you already have the basic markup to make an html page!
(4) Click again on the Normal tab to go back to the Normal view (of no content at the moment).
(5) Assume you are making a home page for yourself. Make a title and some introductory text. Use
regular word processor features like marking your title as Heading 1 in the drop down box on a
menu bar. (The drop down menu may start off displaying ’Paragraph’ or ’Body Text’.) You can
select text and make it bold or italics; enlarge it ... using the editing menu or icons.
(6) Before getting too carried away, save your document as index.html in the existing www directory
under your earlier Python examples. It will save a lot of trouble if you keep your web work together
in this www directory, where I have already placed a number of files yo will want to keep together
in one directory.
(7) Just for comparison, switch back and forth between the Normal and Source views to see all that
has gone on underneath your view, particularly if you edited the format of your text. Somewhere
embedded in the Source view you should see all the text you entered. Some individual characters
have special symbols in HTML that start with an ampersand and end with a semicolon. Again, at
this point it is more important the understand that there are two different views than to be able
to reproduce the Source view from memory.
(8) You can use your web browser to see how your file looks outside the editor. The easiest way to
do this is to go to the web browser’s File menu and Open File entry, and find the index.html file.
It should look pretty similar to the way it looked in Kompozer, but if you put in hyperlinks, they
should now be active.
4.2. COMPOSING WEB PAGES IN PYTHON 128
The discussion of web page editing continues in Section 4.3.4, on html forms, but first we get Python into
the act.
4.1.3. Editing and Testing Different Document Formats. In this chapter you will be working
with several different types of documents that you will edit and test in very different ways. The ending of
their names indicate their use. Each time a new type of file is discussed in later sections, the proper ways to
work with it will be repeated, but with all the variations, it is useful to group them all in one place now:
...Web.py: My convention for regular Python programs taking all their input from the keyboard,
and producing output displayed on a web page. These programs can be run like other Python
programs, directly from an operating system folder or from inside Idle.
...html: Web documents most often composed in an editor like Kompozer. By my convention, these
are split into two categories
...Template.html or ...Output.html: are not intended to be displayed directly in a browser,
but instead are read by a Python program (...cgi or ...Web.py) to create a template or format
string for a final web page that is dynamically generated inside the Python program.
Other: files ending in .html are intended to be directly viewed in a web browser. Except for the
simple static earlier examples in Section 4.1.2, they are designed to reside on a web server,
where they can pass information to a Python CGI program. To make this work on your
computer:
(1) Have all the web pages in the same directory as the example program localCGIServer.py
(2) Have localCGIServer.py running, started from a directory window, not from inside Idle
(3) In the browser URL field, the web page file name must be preceded by http://localhost:8080/.
For example, http://localhost:8080/adder.html would refer to the file adder.html, in the
same directory as the running localCGIServer.py.
...cgi: Python CGI programs, intended to be run from a web server. It is sometimes useful to access
a CGI program directly in a browser. To run on your computer, like the Other html files above, you
need to refer to a URL starting with http://localhost:8080/. For example, http://localhost:8080/now.cgi
would call the file now.cgi, which must be in the same directory as the running localCGIServer.py.
More often CGI programs are referenced in a web form, and the program is called indirectly by the
web server. CGI programs can be edited and saved inside Idle, but they do not run properly from
inside Idle.
def main():
browseLocal(contents, ’helloPython.html’)
main()
This program encapsulates two basic operations into the last two functions that will be used over and over.
The first, strToFile, has nothing new, it just puts specified text in a file with a specified name. The second,
browseLocal, does more. It takes specified text (presumably a web page), puts it in a file, and directly
displays the file in your web browser. It uses the open function from the webbrowser module to start the
new page in your web browser.
In this particular program the text that goes in the file is just copied from the literal string named
contents in the program.
This is no advance over just opening the file in the browser directly! Still, it is a start towards the aim
of creating web content dynamically.
An early example in this tutorial displayed the fixed ’Hello World!’ to the screen. This was later
modified in hello_you4.py to incorporate user input using the string format method of Section 1.12.2,
person = input(’Enter your name: ’)
greeting = ’Hello {person}!’.format(**locals())
print(greeting)
Similarly, I can turn the web page contents into a format string, and insert user data. Load and run the
www example program helloWeb2.py.
The simple changes from helloWeb1.py are marked at the beginning of the file and shown below. I
modified the web page text to contain ’Hello, {person}!’ in place of ’Hello, World!’, making the
string into a format string, which I renamed to the more appropriate pageTemplate. The changed initial
portion with the literal string and and the main program then becomes
pageTemplate = ’’’
4.2. COMPOSING WEB PAGES IN PYTHON 130
def main():
person = input(’Enter a name: ’) # NEW
contents = pageTemplate.format(**locals()) # NEW
browseLocal(contents, ’helloPython2.html’) # NEW filename
incorporating the person’s name into the contents for the web page before saving and displaying it.
In this case, I stored the literal format string inside the Python program, but consider a different
approach:
Load and run the www example program helloWeb3.py. It behaves exactly like helloWeb2.py, but is
slightly different internally – it does not directly contain the web page template string. Instead the web page
template string is read from the file helloTemplate.html.
Below is the beginning of helloWeb3.py, showing the only new functions. The first, fileToStr, will be
a standard function used in the future. It is the inverse of strToFile.
The main program obtains the input. In this simple example, the input is used directly, with little
further processing. It is inserted into the web page, using ’helloTemplate.html’ as a format string.
def main():
person = input(’Enter a name: ’)
contents = fileToStr(’helloTemplate.html’).format(**locals()) # NEW
browseLocal(contents, ’helloPython3.html’) # NEW filename
Although helloTemplate.html is not intended to be viewed by the user (being a template), you should open
it in a web editor (like Kompozer) to look at it. It is legal to create a web page in a web page editor with
expressions in braces embedded in it! If you look in the source view in Kompozer you will see something
similar to the literal string in helloWeb2.py, except the lines are broken up differently. (This makes no
difference in the formatted result, since in html, a newline is treated the same way as a space.)
Back in the Normal mode, add some formatting like italics, and an extra line of text, and save the file
again (under the same name). Run the program helloWeb3.py again, and see that you have been able to
change the appearance of the output without changing the Python program itself. That is the aim of using
the template html page, allowing the web output formatting to be managed mostly independently from the
Python program.
A more complicated but much more common situation is where the input data is processed and trans-
formed into results somehow, and these results, often along with some of the original input, are embedded
in the web page that is produced.
As a simple example, load and run the www example program additionWeb.py, which uses the template
file additionTemplate.html.
4.3. CGI - DYNAMIC WEB PAGES 131
The aim in the end of this chapter is to have user input come from a form on the web rather than the
keyboard on a local machine, but in either case the input is still transformed into results and all embedded
in a web page. To make parts easily reusable, I obtain the input in a distinct place from where the input is
processed. In keeping with the later situation with web forms, all input is of string type ( using keyboard
input for now).
Look at the program. You will see only a few new lines! Because of the modular design, most of the
program is composed of recent standard functions reused.
The only new code is at the beginning and is shown here:
def processInput(numStr1, numStr2): # NEW
’’’Process input parameters and return the final page as a string.’’’
num1 = int(numStr1) # transform input to output data
num2 = int(numStr2)
total = num1+num2
return fileToStr(’additionTemplate.html’).format(**locals())
(2) An action instruction is stored in the form saying what to do when you press a button indicating
you are ready to process the data (the Find Sum button in this case).
(3) In the cases we consider in this tutorial, the action is given as a web resource, giving the location
of a CGI script on some server (in our cases, the same directory on the server as the current web
page).
(4) When you press the button, the browser sends the data that you entered to that web location (in
this case adder.cgi in the same folder as the original web page).
(5) The server recognizes the web resource as an executable script, sees that it is a Python program,
and executes it, using the data sent along from the browser form as input.
(6) The script runs, manipulates its input data into some results, and puts those results into the text
of a web page that is the output of the program.
(7) The server captures this output from the program and sends it back to your browser as a new page
to display.
(8) You see the results in your browser.
This also works locally, entirely on your own computer, using a simple server built into Python. (Internet
no longer needed!)
In an operating system file window, go to the folder with the www examples. Double click on localCGIServer.py
to start the local, internal, web server. You should see a console window pop up, saying “Localhost CGI
server started” . Once the server is started, leave the console window there as long as you want the local
server running. Do not start the local server running from inside Idle.
Caution: If the server aborts and gives an error message about spaces in the path, look at the path
through the parent directories over this www directory. If any of the directory names have spaces in them,
the local file server will not work. Either go up the directory chain and alter the directory names to eliminate
spaces or move the examples directory to a directory that does not have this issue. In particular, you need
to move your examples directory if it is under the ’My Programs’ directory.
Back in the www directory,
(1) Open the web link http://localhost:8080/adder.html (preferably in a new window, separate
from this this tutorial).
(2) You should see an adder form in your browser again. Note that the web address no longer includes
’cs.luc.edu’. Instead it starts with ’localhost:8080’, to reference the local Python server you started.
Fill out the form and test it as before.
(3) Look at the console window. You should see a log of the activity with the server. Close the server
window.
(4) Reload the web link http://localhost:8080/adder.html. You should get an error, since you
refer to localhost, but you just stopped the local server.
For the rest of this chapter, we will be wanting to use the local server, so restart localCGIServer.py, and
keep it going.
4.3.2. A Simple Buildup. Before we get too complicated, consider the source code of a couple of
even simpler examples.
4.3.2.1. hellotxt.cgi. The simplest case is a CGI script with no input. The script just generates plain
text, rather than HTML. Assuming you have your local server going, you can go to the link for hellotxt.cgi,
http://localhost:8080/hellotxt.cgi. The code is in the www example directory, hellotxt.cgi, and
below for you to read:
#!/usr/bin/python
# Required header that tells the browser how to render the text.
print("Content-Type: text/plain\n\n") # here text -- not html
the one Loyola’s Computer Science Department uses). The line is ignored in Windows. If you leave the line
there as a part of your standard text, you have one less thing to think about when uploading to a Unix
server.
The first print function is telling the server which receives this output, that the format of the rest of
the output will be plain text. This information gets passed back to the browser later. This line should be
included exactly as stated IF you only want the output to be plain text (the simplest case, but not our usual
case).
The rest of the output (in this case just from one print function) becomes the body of the plain text
document you see on your browser screen, verbatim since it is plain text. The server captures this output
and redirects it to your browser.
4.3.2.2. hellohtml.cgi. We can make some variation and display an already determined html page rather
than plain text. Try the link http://localhost:8080/hellohtml.cgi. The code is in the www example
directory, hellohtml.cgi, and below for you to read:
#!/usr/bin/python
print("""
<html>
<Title>Hello in HTML</Title>
<body>
<p>Hello There!</p>
<p><b>Hi There!</b></p>
</body>
</html> """)
There are two noteworthy changes. The first print function now declares the rest of the output will be html.
This is the standard line you will be using for your CGI programs. The remaining print function has the
markup for an html page. Note that the enclosing triple quotes work for a multi line string. Other than as
a simple illustration, this CGI script has no utility: Just putting the contents of the last print function in a
file for a static web page hello.html is much simpler.
4.3.2.3. now.cgi. One more simple step: we can have a CGI script that generates dynamic output by
reading the clock from inside of Python: Try the link http://localhost:8080/now.cgi. Then click the
refresh button and look again. This cannot come from a static page. The code is in the www example
directory, now.cgi, and below for you to read:
#!/usr/bin/python
import time
print("Content-Type: text/html\n\n") # html markup follows
htmlFormat = """
<html>
<Title>The Time Now</Title>
<body>
<p>The current Central date and time is: {timeStr}</p>
</body>
</html> """
This illustrates a couple more ideas: First a library module, time, is imported and used to generate the
string for the current date and time.
4.3. CGI - DYNAMIC WEB PAGES 134
The web page is generated like in helloWeb2.py, embedding the dynamic data (in this case the time)
into a literal web page format string. (Note the embedded {timeStr}.) Unlike helloWeb2.py, this is a CGI
script so the web page contents are delivered to the server just with a print function.
4.3.2.4. adder.cgi. It is a small further step to processing dynamic input. Try filling out and submitting
the adder form one more time, http://localhost:8080/adder.html. This time notice the URL at the
top of the browser page when the result is displayed. You should see something like the following (only the
numbers should be the ones you entered):
http://localhost:8080/adder.cgi?x=24&y=56
This shows one mechanism to deliver data from a web form to the CGI script that processes it.
The names x and y are used in the form (as we will see later) and the data you entered is associated
with those names. In fact a form is not needed at all to create such an association: If you directly go
to the URLs http://localhost:8080/adder.cgi?x=24&y=56 or http://localhost:8080/adder.cgi?x=
-12345678924&y=33333333333, you get arithmetic displayed without the form. This is just a new input
mechanism into the CGI script.
You have already seen a program to produce this adder page from inside a regular Python program
taking input from the keyboard. The new CGI version, adder.cgi, only needs to make a few modifications to
accept input this way from the browser. New features are commented in the source and discussed below. The
new parts are the import statement through the main function, and the code after the end of the fileToStr
function. Read at least these new parts in the source code shown below:
#!/usr/bin/python
# use format of next two lines with YOUR names and default data
numStr1 = form.getfirst("x", "0") # get the form value associated with form
# name ’x’. Use default "0" if there is none.
numStr2 = form.getfirst("y", "0") # similarly for name ’y’
contents = processInput(numStr1, numStr2) # process input into a page
print(contents)
try: # NEW
print("Content-type: text/html\n\n") # say generating html
main()
except:
cgi.print_exception() # catch and print errors
First the overall structure of the code:
4.3. CGI - DYNAMIC WEB PAGES 135
adder.cgi and the processInput code from your quotientWeb.py. You can keep the same browser data names,
x and y, as in adder.cgi, so the main method should not need changes from adder.cgi. Remember to test
for syntax errors inside Idle, and to have the local web server running when you run the CGI script in
your browser. Since we have not yet covered web forms, test your CGI script by entering test data into
the URL in the browser, like by going to links http://localhost:8080/quotient.cgi?x=24&y=56 and
http://localhost:8080/quotient.cgi?x=36&y=15. After trying these links, you can edit the numbers in
the URL in the browser to see different results.
4.3.4. Editing HTML Forms. This section is a continuation of Section 4.1.2. It is about HTML
editing, not Python. HTML forms will allow user-friendly data entry for Python CGI scripts. This is the
last elaboration to allow basic web interaction: Enter data in a form, submit it, and get a processed result
back from the server.
The initial example, adder.html, used only two text fields. To see more common form fields, open
http://localhost:8080/commonFormFields.html. (Make sure your local server is still running!)
To allow easy concentration on the data sent by the browser, this form connects to a simple CGI script
dumpcgi.cgi, that just dumps all the form data to a web page. Press the submit button in the form, and
see the result. Back up from the output to the previous page, the form, and change some of the data in all
kinds of fields. Submit again and see the results. Play with this until you get the idea clearly that the form
is passing on your data.
To play with it at a deeper level, open this same file, the www example commonFormFields.html, in
Kompozer. The static text in this page is set up as a tutorial on forms in Kompozer. Read the content
of the page describing how to edit the overall form and each type of individual field. Textbooks such as
the Analytical Engine give another discussion of some of the attributes associated with each field type.
Read the static text about how to edit individual fields, and change some field parameters, save the file
and reload it in your browser, and submit again. If you change the name or value attributes, they are
immediately indicated in the dumped output. If you change things like the text field size, it makes a change
in the way the form looks and behaves. You can return to the original version: An extra copy is saved in
commonFormFieldsOrig.html.
Now open adder.html in Kompozer. Switch to the Source view. This is a short enough page that you
should not get lost in the source code. The raw text illustrates another feature of html: attributes. The
tag to start the form contains not only the tag code form, but also several expressions that look like Python
assignment statements with string values. The names on the left-hand side of the equal signs identify a type
of attribute, and the string value after the equal sign gives the corresponding value for the attribute. The
tag for many kinds of input fields is input. Notice that each field includes name and value attributes. See
that the ’x’ and ’y’ that are passed in the URL by the browser come from the names given in the HTML
code for the corresponding fields.
Kompozer and other web editors translate your menu selections into the raw html code with proper
attribute types. This high level editor behavior is convenient to avoid having to learn and debug the exact
right html syntax! On the other hand, using pop-up field editing windows has the disadvantage that you
can only see the attributes of one field at a time. Particularly if you want to modify a number of name or
value attributes, it is annoying that you need a number of mouse clicks to go from one field to the next. If
you only want to modify the values of existing attributes like name and value, it may be easier to do in the
source window, where you can see everything at once. Making syntax errors in not very likely if you only
change data in quoted value strings.
The action URL is a property of the entire form. To edit it in Kompozer, right click inside the form, but
not on any field element, and select the bottom pop-up choice, Form Properties. Then you see a window
listing the Action URL and you can change the value to the name of the CGI script that you want to
receive the form data. When you create your own web form, I suggest you make the initial action URL be
dumpcgi.cgi. This will allow you to debug your form separate from your CGI script. When you have tested
that your web form has all the right names and initial values, you can change the action URL to your CGI
script name (like quotient.cgi), and go on to test the combination of the form and the CGI script!
Exercise 4.3.4.1. ** Complete the web presentation for quotient.cgi of Exercise 4.3.3.1 by creating a
web form quotient.html that is intelligible to a user and which supplies the necessary data to quotient.cgi.
4.3. CGI - DYNAMIC WEB PAGES 137
Be sure to test the new form on your local server! Remember that you must have the local server
running first. You must have all the associated files in the same directory as the server program you are
running, and you cannot just click on quiotient.html in a file browser. You must start it from the the URL
http://localhost:8080/quotient.html, that specifically refers to the server localhost.
Exercise 4.3.4.2. ** Make a simple complete dynamic web presentation with a CGI script that uses at
least three user inputs from a form. The simplest would be to just add three numbers instead of two. Call
your form dynamic.html. Call your CGI script dynamic.cgi. Call an output template dynamicTemplate.html.
remember the details listed in the previous exercise to make the results work on localhost. After the server
is started and you have all the files, go to http://localhost:8080/dynamic.html.
The Summary Section 4.4 starts with the overall process for creating dynamic web pages.
4.3.5. More Advanced Examples. One of the advantages of having a program running on a public
server is that data may be stored centrally and augmented and shared by all. In high performance sites
data is typically stored in a sophisticated database, beyond the scope of this tutorial. For a less robust but
simpler way to store data persistently, we can use simple text files on the server.
The www example page namelist.html uses namelist.cgi to maintain a file namelist.txt of data submitted
by users of the page. You can test the program with your local Python server. It is less impressive when you
are the only one who can make changes! You may also try the copy on the public Loyola server, http://
cs.luc.edu/anh/python/hands-on/examples/www/namelist.html. The local source code is documented
for those who would like to have a look.
You also may want to look at the source code of the utility script you have been using, dumpcgi.cgi. It
uses a method of getting values from the CGI data that has not been discussed:
val = form.getlist(name)
This method returns a list of values associated with a name from the web form. The list many have, 0, 1,
or many elements. It is needed if you have a number of check boxes with the same name. (Maybe you want
a list of all the toppings someone selects for a pizza.)
Both dumpcgi.cgi and namelist.html add an extra layer of robustness in reflecting back arbitrary text
from a user. The user’s text may include symbols used specially in html like ’<’. The function safePlainText
replaces reserved symbols with appropriate alternatives.
The examples in earlier sections were designed to illustrate the flow of data from input form to output
page, but neither the html or the data transformations have been very complicated. A more elaborate
situation is ordering pizza online, and recording the orders for the restaurant owner. You can try http:
//localhost:8080/pizza1.cgi several times and look at the supporting example www files pizza1.cgi,
pizzaOrderTemplate1.html, and the simple pizzaReportTemplate.html. To see the report, the owner needs
to know the special name owner777. After ordering several pizzas, enter that name and press the Submit
button again.
This script gets used in two ways by a regular user: initially, when there is no order, and later to confirm
an order that has been submitted. The two situations use different logic, and the script must distinguish
what is the current use. A hidden variable is used to distinguish the two cases: when pizza1.cgi is called
directly (not from a form), there is no pastState field. On the other hand the pizzaOrderTemplate1.html
includes a hidden field named pastState, which is set to the value ’order’. (You can confirm this by examining
the end of the page in Kompozer’s source mode.) The script checks the value of the field pastState, and
varies its behavior based on whether the value is ’order’ or not.
The form in pizzaOrderTemplate1.html has radio buttons and check boxes hard coded into it for the
options, and copies of the data are in pizza1.cgi. Keeping multiple active copies of data is not a good idea:
They can get out of sync. If you look at the source code for pizzaOrderTemplate1.html, you see that all
the entries for the radio button and check box lines are in a similar form. In the better version with altered
files pizza.cgi and pizzaOrderTemplate.html (that appears the same to the user), the basic data for the pizza
options is only in one place in pizza.cgi, and the proper number of lines of radio buttons and check boxes
with the right data are generated dynamically.
A further possible elaboration would be to also allow the restaurant manager to edit the size, cost and
topping data online, and store the data in a file rather than having the data coded in pizza.cgi, so if the
4.4. SUMMARY 138
manager runs out of a topping, she can remove it from the order form. this change would be a fairly elaborate
project compared to the earlier exercises!
Final www examples are a pair of programs in real use in my courses. To illustrate, you can try the
sample survey, http://localhost:8080/pythonTutorialsurvey.html. Run it several times with different
responses. Forms can be set up like this one to link to the www example CGI script surveyFeedback.cgi,
which will save any number of responses to the survey. After getting responses you can start the Idle shortcut
in the www example directory and run the regular Python program, readFeedback.py, which is also in the
www example directory: At the prompt for a survey base name, enter exactly:
pythonTutorial
Then the program prints out all the survey feedback, grouped in two different ways. It documents the use of
a couple of modules not introduced in this tutorial, but the rest just uses ideas from the tutorial, including
considerable emphasis on dictionaries and string processing.
4.4. Summary
(1) The Overall Process for Creating Dynamic Web Pages
Making dynamic web pages has a number of steps. I have suggested several ways of decoupling
the parts, so you can alter the order, but if you are starting from nothing, you might follow the
following sequence:
(a) Determine the inputs you want to work with and make a web form that makes it easy and
obvious for the user to provide the data. You may initially want to have the form’s action
URL be dumpcgi.cgi, so you can debug the form separately. Test with the local server. When
everything seems OK, make sure to change the action URL to be the name of the CGI script
you are writing. [4.3.4]
(b) It is easier to debug a regular Python program totally inside Idle than to mix the Idle editor
and server execution. Particularly if the generation of output data is going to be complicated
or there are lots of places you are planning to insert data into an output template, I suggest
you write the processInput function with its output template first and test it without a
server, as we did with additionWeb.py, providing either canned input in the main program,
or taking input data from the keyboard with input, and saving the output page to a local file
that you examine in your webbrowser. [4.2.1]
(c) When you are confident about your processInput function, put it in a program with the
proper cgi skeleton, and add the necessary lines at the beginning of the main function to take
all the CGI script input from the browser data. [4.3.2.4]
(d) Finally test the whole thing with the local server.
(e) If you have an account on a public server, it should not take much more work than just
uploading your files to make your creation available to the whole world. You may have a
public server with a different configuration than the Loyola server. If so see this note:1
(2) Markup: Plain text may be marked up to include formatting. The formatting may be only easily
interpreted by a computer, or it may be more human readable. One form of human-readable
markup is hypertext markup language (HTML). [4.1.1]
(a) HTML markup involves tags enclosed in angle braces. Ending tags start with ’/’. For instance
<title>Computer Science</title>.
(i) Tags may be modified with attributes specified similar to Python string assignments,
for example the text input field tag,
<input value="red" name="color" type="radio">
(b) Modern editors allow HTML to be edited much like in a word processor. Two views of the
data are useful: the formatted view and the source view, showing the raw HTML markup.
1The tutorial assumed a server configured as follows: html pages and CGI scripts can all be in the same directory, and
the CGI scripts end with .cgi. This is the convention on Loyola’s Computer Science public student server. Another common
configuration is that scripts all go in a cgi-bin directory, where they just have the normal .py suffix. If you have a server with
the latter configuration, your action URLs will be of the form cgi-bin/someScript.py. Depending on the server configuration the
current directory may or may not be cgi-bin while the script executes. That may mean you need a path before the file names
for your output templates, or your need to be careful what directory referenced files end up in. If you are making arrangements
for your own site on a public server, be sure to check with your system administartor to find out what the conventions are.
4.4. SUMMARY 139
(3) Python and HTML: Since HTML is just a text string, it can easily be manipulated in Python, and
read and written to text files. [4.2.1]
(4) The webbrowser module has a function open, that will open a file or web URL in the default
browser: [4.2.1]
webbrowser.open(filename)
(5) Common Gateway Interface (CGI). The sequence of events for generating a dynamic web page via
CGI: [4.3.1]
(a) The data a user types is handled directly by the browser. It recognizes forms.
(b) The user presses a Submit button. An action is stored in the form saying what to do when
the button is pressed.
(c) In the cases we consider in this tutorial, the action is given as a web resource, giving the
location of a CGI script on some server. The browser sends the data that you entered to that
web location.
(d) The server recognizes the page as an executable script, sees that it is a Python program, and
executes it, using the data sent along from the browser form as input.
(e) The script runs, manipulates the input data into some results, and puts those results into the
text of a web page that is the output of the program.
(f) The server captures this output from the program and send it back to the user’s browser as a
new page to display.
(g) The results appear in the user’s browser.
(6) The cgi Module
(a) Create the object to process CGI input with [4.3.2.4]
form = cgi.FieldStorage()
(b) Extract the first value specified by the browser with name nameAttrib, or use default if no
such value exists [4.3.2.4]
variable = form.getfirst(nameAttrib, default)
(c) Extract the list of all values specified by the browser associated with name, nameAttrib
[4.3.5]
listVariable = form.getlist(nameAttrib)
This case occurs if you have a number of checkboxes, all with the same name, but different
values.
(7) Local Python Servers.
(a) Python has modules for creating local testing servers that can handle static web pages and
Python CGI scripts.[4.3.1]
(b) Different kinds of errors with CGI scripts are handled different ways by a local Python server.
[4.3.3]
(8) A comparison of the various types of files used in web programming, listing the different ways to
edit and use the files, is given in Section 4.1.3.