0% found this document useful (0 votes)
29 views

Py Tutorial 85 112

This document discusses creating graphical user interfaces using the graphics module in Python. It describes how to create entry boxes that allow for user input, and how to retrieve the text entered. Functions are used to simplify code for creating multiple labeled entry boxes. Random colors can be generated by choosing random red, green, and blue values between 0-255 and passing them to the color_rgb function. The document includes code examples to demonstrate these concepts.

Uploaded by

dyner24tests
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
29 views

Py Tutorial 85 112

This document discusses creating graphical user interfaces using the graphics module in Python. It describes how to create entry boxes that allow for user input, and how to retrieve the text entered. Functions are used to simplify code for creating multiple labeled entry boxes. Random colors can be generated by choosing random red, green, and blue values between 0-255 and passing them to the color_rgb function. The document includes code examples to demonstrate these concepts.

Uploaded by

dyner24tests
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 28

2.4.

GRAPHICS 85

main()
The only part of this with new ideas is:
entry1 = Entry(Point(winWidth/2, 200),10)
entry1.draw(win)
Text(Point(winWidth/2, 230),’Name:’).draw(win) # label for the Entry

win.getMouse() # To know the user is finished with the text.


name = entry1.getText()
The first line of this excerpt creates an Entry object, supplying its center point and a number of characters
to leave space for (10 in this case).
As with other places where input is requested, a separate static label is added.
The way the underlying events are hidden in graphics.py, there is no signal when the user is done entering
text in an Entry box. To signal the program, a mouse press is used above. In this case the location of the
mouse press is not relevant, but once the mouse press is processed, execution can go on to reading the Entry
text. The method name getText is the same as that used with a Text object.
Run the next example, addEntries.py, also copied below:
"""Example with two Entry objects and type conversion.
Do addition.
"""

from graphics import *

def main():
winWidth = 300
winHeight = 300

win = GraphWin("Addition", winWidth, winHeight)


win.setCoords(0,0, winWidth, winHeight)
instructions = Text(Point(winWidth/2, 30),
"Enter two numbers.\nThen click the mouse.")
instructions.draw(win)

entry1 = Entry(Point(winWidth/2, 250),25)


entry1.setText(’0’)
entry1.draw(win)
Text(Point(winWidth/2, 280),’First Number:’).draw(win)

entry2 = Entry(Point(winWidth/2, 180),25)


entry2.setText(’0’)
entry2.draw(win)
Text(Point(winWidth/2, 210),’Second Number:’).draw(win)

win.getMouse() # To know the user is finished with the text.

numStr1 = entry1.getText()
num1 = int(numStr1)
numStr2 = entry2.getText()
num2 = int(numStr2)
result = "The sum of\n{num1}\nplus\n{num2}\nis {sum}.".format(**locals())
Text(Point(winWidth/2, 110), result).draw(win)

instructions.setText("Click anywhere to quit.")


2.4. GRAPHICS 86

win.getMouse()
win.close()

main()
There is not a separate graphical replacement for the input statement, so you only can read strings. With
conversions, it is still possible to work with numbers.
Only one new graphical method has been included above:
entry1.setText(’0’)
Again the same method name is used as with a Text object. In this case I chose not to leave the Entry
initially blank. The 0 value also reinforces that a numerical value is expected.
There is also an entry2 with almost identical code. After waiting for a mouse click, both entries are
read, and the chosen names emphasizes they are strings. The strings must be converted to integers in order
to do arithmetic and display the result.
The almost identical code for the two entries is a strong suggestion that this code could be written more
easily with a function. You may look at the identically functioning example program addEntries2.py. The
only changes are shown below. First there is a function to create an Entry and a centered static label over
it.

def makeLabeledEntry(entryCenterPt, entryWidth, initialStr, labelText, win):


’’’Return an Entry object with specified center, width in characters, and
initial string value. Also create a static label over it with
specified text. Draw everything in the GraphWin win.
’’’

entry = Entry(entryCenterPt, entryWidth)


entry.setText(initialStr)
entry.draw(win)
labelCenter = entryCenterPt.clone()
labelCenter.move(0, 30)
Text(labelCenter,labelText).draw(win)
return entry
In case I want to make more Entries with labels later, and refer to this code again, I put some extra effort
in, making things be parameters even if only one value is used in this program. The position of the label is
made 30 units above the entry by using the clone and move methods. Only the Entry is returned, on the
assumption that the label is static, and once it is drawn, I can forget about it.
Then the corresponding change in the main function is just two calls to this function:
entry1 = makeLabeledEntry(Point(winWidth/2, 250), 25,
’0’, ’First Number:’, win)
entry2 = makeLabeledEntry(Point(winWidth/2, 180), 25,
’0’, ’Second Number:’, win)
These lines illustrate that a statement may take more than one line. In particular, as in the Shell, Python
is smart enough to realize that there must be a continuation line if the parentheses do not match.
While I was improving things, I also changed the conversions to integers. In the first version I wanted
to emphasize the existence of both the string and integer data as a teaching point, but the num1Str and
num2Str variables were only used once, so a more concise way to read and convert the values is to eliminate
them:
num1 = int(entry1.getText())
num2 = int(entry2.getText())

2.4.10. Color Names. Thus far we have only used common color names. In fact there are a very large
number of allowed color names, and the abiltity to draw with custom colors.
2.4. GRAPHICS 87

First, the graphics package is built on an underlying graphics system, Tkinter, which has a large number
of color names defined. Each of the names can be used by itself, like ’red’, ’salmon’ or ’aquamarine’ or with
a lower intensity by specifying with a trailing number 2, 3, or 4, like ’red4’ for a dark red.
Though the ideas for the coding have not all been introduced, it is still informative to run the example
program colors.py. As you click the mouse over and over, you see the names and appearances of a wide
variety of built-in color names. The names must be place in quotes, but capitalization is ignored.
2.4.11. Custom Colors. Custom colors can also be created. To do that requires some understanding
of human eyes and color (and the Python tools). The only colors detected directly by the human eyes are
red, green, and blue. Each amount is registered by a different kind of cone cell in the retina. As far as the
eye is concerned, all the other colors we see are just combinations of these three colors. This fact is used
in color video screens: they only directly display these three colors. A common scale to use in labeling the
intensity of each of the basic colors (red, green, blue) is from 0 to 255, with 0 meaning none of the color, and
255 being the most intense. Hence a color can be described by a sequence of red, green, and blue intensities
(often abbreviated RGB). The graphics package has a function, color_rgb, to create colors this way. For
instance a color with about half the maximum red intensity, no green, and maximum blue intensity would
be
aColor = color_rgb(128, 0, 255)
Such a creation can be used any place a color is used in the graphics, (i.e. circle.setFill(aColor) ).
2.4.12. Random Colors. Another interesting use of the color_rgb function is to create random
colors. Run example program randomCircles.py. The code also is here:
"""Draw random circles.
"""

from graphics import *


import random, time

def main():
win = GraphWin("Random Circles", 300, 300)
for i in range(75):
r = random.randrange(256)
b = random.randrange(256)
g = random.randrange(256)
color = color_rgb(r, g, b)

radius = random.randrange(3, 40)


x = random.randrange(5, 295)
y = random.randrange(5, 295)

circle = Circle(Point(x,y), radius)


circle.setFill(color)
circle.draw(win)
time.sleep(.05)

Text(Point(150, 20), "Click to close.").draw(win)


win.getMouse()
win.close()

main()
Read the fragments of this program and their explanations:
To do random things, the program needs a function from the random module. This example shows that
imported modules may be put in a comma separated list:
import random, time
2.5. FILES 88

You have already seen the built-in function range. To generate a sequence of all the integers 0, 1, ... 255,
you would use
range(256)
This is the full list of possible values for the red, green or blue intensity parameter. For this program
we randomly choose any one element from this sequence. Instead of the range function, use the random
module’s randrange function, as in
r = random.randrange(256)
b = random.randrange(256)
g = random.randrange(256)
color = color_rgb(r, g, b)
This gives randomly selected values to each of r, g, and b, which are then used to create the random color.
I want a random circle radius, but I do not want a number as small as 0, making it invisible. The range
and randrange functions both refer to a possible sequence of values starting with 0 when a single parameter
is used. It is also possible to add a different starting value as the first parameter. You still must specify a
value past the end of the sequence. For instance
range(3, 40)
would refer to the sequence 3, 4, 5, ... , 39 (starting with 3 and not quite reaching 40). Similarly
random.randrange(3, 40)
randomly selects an arbitrary element of range(3, 40).
I use the two-parameter version to select random parameters for a Circle:
radius = random.randrange(3, 40)
x = random.randrange(5, 295)
y = random.randrange(5, 295)

circle = Circle(Point(x,y), radius)


7
What are the smallest and largest values I allow for x and y?
Random values are often useful in games.
Exercise 2.4.12.1. * Write a program ranges.py that uses the range function to produce the sequnce
1, 2, 3, 4, and then print it. Also prompt the user for an integer n and print the sequnce 1, 2, 3, ... , n –
including n. Hint: 8 Finally use a simple repeat loop to find and print five randomly chosen numbers from
the range 1, 2, 3, ... , n .

2.5. Files
This section fits here logically (as an important built-in type of object) but it is not needed for the next
chapter, Flow of Control, 3.
Thus far you have been able to save programs, but anything produced during the execution of a program
has been lost when the program ends. Data has not persisted past the end of execution. Just as programs
live on in files, you can generate and read data files in Python that persist after your program has finished
running.
As far as Python is concerned, a file is just a string (often very large!) stored on your file system, that
you can read or write gradually or all together.
Open a directory window for your Python program directory. First note that there is no file named
sample.txt.
Make sure you have started Idle so the current directory is your Python program directory (for instance
in Windows with the downloaded shortcut to Idle).
Run the example program firstFile.py, shown below:
outFile = open(’sample.txt’, ’w’)
outFile.write(’My first output file!’)
outFile.close()
75 and 294 (one less than 295).
8If 4 or n is the last number, what is the first number past the end of the sequence?
2.5. FILES 89

The first line creates a file object, which links Python to your computer’s file system. The first parameter in
the file constructor gives the file name, sample.txt. The second parameter indicates how you use the file.
The ’w’ is short for write, so you will be creating and writing to a file (or if it already existed, destroying
the old contents and starting over!). If you do not use any operating system directory separators in the
name (’\’ or ’/’ depending on your operating system), then the file will lie in the current directory. The
assignment statement gives the python file object the name outFile.
The second line writes the specified string to the file.
The last line is important to clean up. Until this line, this Python program controls the file, and nothing
may even be actually written to the file. (Since file operations are thousands of times slower than memory
operations, Python buffers data, saving small amounts and writing all at once in larger chunks.) The close
line is essential for Python to make sure everything is really written, and to relinquish control of the file. It
is a common bug to write a program where you have the code to add all the data you want to a file, but the
program does not end up creating a file. Usually this means you forgot to close the file.
Now switch focus and look at a file window for the current directory. You should now see a file
sample.txt. You can open it in Idle (or your favorite word processor) and see its contents.
Run the example program nextFile.py, shown below, which has two calls to the write method:
outFile = open(’sample2.txt’, ’w’)
outFile.write(’My second output file!
outFile.write(’Write some more.’)
outFile.close()
Now look at the file, sample2.txt. Open it in Idle. It may not be what you expect! The write method for
the file is not quite like a print function. It does not add anything to the file except exactly the data you
tell it to write. If you want a newline, you must indicate it explicitly. Recall the newline code \n. Run the
example program revisedFile.py, shown below, which adds newline codes:
outFile = open(’sample3.txt’, ’w’)
outFile.write(’A revised output file!\n’)
outFile.write(’Write some more.\n’)
outFile.close()
Check the contents of sample3.txt. This manner of checking the file shows it is really in the file system,
but the focus in the Tutorial should be on using Python! Run the example program printFile.py, shown
below:
inFile = open(’sample3.txt’, ’r’)
contents = inFile.read()
print(contents)
Now you have come full circle: what one Python program has written into the file sample3.txt, another has
read and displayed.
In the first line an operating system file (sample3.txt) is associated again with a Python variable name
(inFile). The second parameter again gives the mode of operation, but this time it is ’r’, short for read.
This file, sample3.txt, should already exist, and the intention is to read from it. This is the most common
mode for a file, so the ’r’ parameter is actually optional.
The read method returns all the file’s data as a single string, here assigned to contents. Using the
close method is generally optional with files being read. There is nothing to lose if a program ends without
closing a file that was being read.9
Exercise 2.5.0.2. Make the following programs in sequence. Be sure to save the programs in the same
directory as where you start the idle shortcut and where you have all the sample text files:
* a. printUpper.py: read the contents of the sample2.txt file and print the contents out in upper case. (This
should use file operations and should work no matter what the contents are in sample2.txt. Do not assume
the particular string written by nextFile.py!)
* b. fileUpper.py: prompt the user for a file name, read and print the contents of the requested file in upper
case.
9If, for some reason, you want to reread this same file while the same program is running, you need to close it and reopen
it.
2.6. SUMMARY 90

** c. copyFileUpper: modify fileUpper.py to write the upper case string to a new file rather than printing
it. Have the name of the new file be dynamically derived from the old name by prepending ’UPPER’ to
the name. For example, if the user specified the file sample.txt (from above), the program would create
a file UPPERsample.txt, containing ’MY FIRST OUTPUT FILE!’. When the user specifies the file name
stuff.txt, the resulting file would be named UPPERstuff.txt.

Exercise 2.5.0.3. Write madlib3.py, a small modification of madlib2.py, requiring only a modification
to the main function of madlib2.py. Your madlib3.py should prompt the user for the name of a file that
should contain a madlib format string as text (with no quotes around it). Read in this file and use it as the
format string in the tellStory function. This is unlike in madlib2.py, where the story is a literal string
coded directly into the program called originalStory. The tellstory function and particularly the getKeys
function were developed and described in detail in this tutorial, but for this exercise there is no need to
follow their inner workings - you are just a user of the tellstory function (and the functions that it calls).
You do not need to mess with the code for the definition of tellStory or any of the earlier supporting
functions. The original madlib string is already placed in a file jungle.txt, that is in this format as an
example. With the Idle editor, write another madlib format string into a file myMadlib.txt. If you earlier
created a file myMadlib.py, then you can easily extract the story from there (without the quotes around it).
Test your program both with jungle.txt and your new madlib story file.

2.6. Summary
The same typographical conventions will be used as in the last summary in Section 1.15.
(1) Object notation
(a) When the name of a type of object is used as a function call, it is called a constructor, and a
new object of that type is constructed and returned. The meanings of any parameters to the
constructor depend on the type. [2.2.3]
(b) object.methodName(parameters)
Objects may have special operations associated with them, called methods. They are functions
automatically applied to the object before the dot. Further parameters may be expected,
depending on the particular method. [2.1.1]
(2) String (str) indexing and methods
See the Chapter 1 Summary Section 1.15 for string literals and symbolic string operations.
(a) String Indexing. [2.1.2]
stringReference[intExpression]
Individual characters in a string may be chosen. If the string has length L, then the indices
start from 0 for the initial character and go to L-1 for the rightmost character. Negative
indices may also be used to count from the right end, -1 for the rightmost character through
-L for the leftmost character. Strings are immutable, so individual characters may be read,
but not set.
(b) String Slices [2.1.3]
stringReference[start : pastEnd]
stringReference[start : ]
stringReference[ : pastEnd]
stringReference[ : ]
A substring or slice of 0 or more consecutive characters of a string may be referred to by
specifying a starting index and the index one past the last character of the substring. If the
starting or ending index is left out Python uses 0 and the length of the string respectively.
Python assumes indices that would be beyond an end of the string actually mean the end of
the string.
(c) String Methods: Assume s refers to a string
(i) s.upper()
Returns an uppercase version of the string s. [2.1.1]
(ii) s.lower()
Returns a lowercase version of the string s.[2.1.1]
2.6. SUMMARY 91

(iii) s.count(sub)
Returns the number of repetitions of the substring sub inside s. [2.1.1]
(iv) s.find(sub)
s.find(sub, start)
s.find(sub, start, end)
Returns the index in s of the first character of the first occurrence of the substring sub
within the part of the string s indicated, respectively the whole string s, s[start : ],
or s[start : end], where start and end have integer values.[2.1.1]
(v) s.split()
s.split(sep)
The first version splits s at any sequence of whitespace (blanks, newlines, tabs) and
returns the remaining parts of s as a list. If a string sep is specified, it is the separator
that gets removed from between the parts of the list. [2.1.5]
(vi) sep.join(sequence)
Return a new string obtained by joining together the sequence of strings into one string,
interleaving the string sep between sequence elements. [2.1.6]
(vii) Further string methods are discussed in the Python Reference Manual, Section 2.3.6.1,
String Methods. [2.1.7]
(3) Sets
A set is a collection of elements with no repetitions. It can be used as a sequence in a for loop.
A set constructor can take any other sequence as a parameter, and convert the sequence to a set
(with no repetitions). Nonempty set literals are enclosed in braces. [2.2.2]
(4) List method append
aList.append(element)
Add an arbitrary element to the end of the list aList, mutating the list, not returning any list.
[2.2.1]

(5) Files [2.5]


file(nameInFileSystem) returns a file object for reading, where nameInFileSystem must be a
string referring to an existing file. The file location is relative to the current directory.
file(nameInFileSystem, ’w’) returns a file object for writing, where the string nameInFileSystem
will be the name of the file. If it did not exist before, it is created. CAUTION: If it did
exist before, all previous contents are erased. The file location is relative to the current
directory.
If infile is a file opened for reading, and outfile is a file opened for writing, then
infile.read() returns the entire file contents of the file as a string.
infile.close() closes the file in the operating system (generally not needed, unless
the file is going to be modified later, while your program is still running).
outfile.write(stringExpression) writes the string to the file, with no extra new-
line.
outfile.close() closes the file in the operating system (important to make sure the
whole file gets written and to allow other access to the file).
(6) Mutable objects [2.4.6]
Care must be taken whenever a second name is assigned to a mutable object. It is an alias for the
original name, and refers to the exact same object. A mutating method applied to either name
changes the one object referred to by both names.

Many types of mutable object have ways to make a copy that is a distinct object. Zelle’s graphical
objects have the clone method. A copy of a list may be made with a full slice: someList[:]. Then
direct mutations to one list (like appending an element) do not affect the other list, but still, each
list is indirectly changed if a common mutable element in the lists is changed.
(7) Graphics
A systematic reference to Zelle’s graphics package, graphics.py, is at http://mcsp.wartburg.edu/
zelle/python/graphics/graphics/index.html.
2.6. SUMMARY 92

(a) Introductory examples of using graphics.py are in [2.4.1], [2.4.2], and [2.4.9]
(b) Windows operating system .pyw
In windows, a graphical program that take no console input and generates no console output,
may be given the extension .pyw to suppress the generation of a console window. [2.4.3]
(c) Event-driven programs
Graphical programs are typically event-driven, meaning the next operation done by the pro-
gram can be in response to a large number of possible operations, from the keyboard or mouse
for instance, without the program knowing which kind of event will come next. For simplicity,
this approach is pretty well hidden under Zelle’s graphics package, allowing the illusion of
simpler sequential programming. [2.4.4]
(d) Custom computer colors are expressed in terms of the amounts of red, green, and blue. [2.4.11]
(e) See also Animation under the summary of Programming Techniques.
(8) Additional programming techniques
(a) These techniques extend those listed in the summary of the previous chapter. [1.15]
(b) Sophisticated operations with substrings require careful setting of variables used as an index.
[2.1.4]
(c) There are a number of techniques to assist creative programming, including pseudo-code and
gradual generalization from concrete examples. [2.3.2]
(d) Animation: a loop involving small moves followed by a short delay (assumes the time module
is imported): [2.4.8
loop heading :
move all objects a small step in the proper direction
time.sleep(delay).
(e) Example of a practical successive modification loop: [2.3.1]
(f) Examples of encapsulating ideas in functions and reusing them: [2.3.1], [2.3.3], [2.4.8]
(g) Random results can be introduced into a program using the random module. [2.4.12]
CHAPTER 3

More On Flow of Control

You have varied the normal forward sequence of operations with functions and for loops. To have full
power over your programs, you need two more constructions that changing the flow of control: decisions
choosing between alternatives (if statements), and more general loops that are not required to be controlled
by the elements of a collection (while loops).

3.1. If Statements
3.1.1. Simple Conditions. The statements introduced in this chapter will involve tests or conditions.
More syntax for conditions will be introduced later, but for now consider simple arithmetic comparisons that
directly translate from math into Python. Try each line separately in the Shell
2 < 5
3 > 7
x = 11
x > 10
2*x < x
type(True)
You see that conditions are either True or False (with no quotes). These are the only possible Boolean
values (named after 19th century mathematician George Boole). In Python the name Boolean is shortened
to the type bool. It is the type of the results of true-false tests.

3.1.2. Simple if Statements. Run this example program, suitcase.py. Try it at least twice, with
inputs: 30 and then 55. As you an see, you get an extra result, depending on the input. The main code is:
weight = float(input(’How many pounds does you suitcase weigh? ’))
if weight > 50:
print(’There is a $25 charge for luggage that heavy.’)
print(’Thank you for your business.’)
The middle two line are an if-statement. It reads pretty much like English. If it is true that the weight is
greater than 50, then print the statement about an extra charge. If it is not true that the weight is greater
than 50, then don’t do the indented part: skip printing the extra luggage charge. In any event, when you
have finished with the if-statement (whether it actually does anything or not), go on to the next statement
that is not indented under the if. In this case that is the statement printing “Thank you”.
The general Python syntax for a simple if statement is
if condition :
indentedStatementBlock
If the condition is true, then do the indented statements. If the condition is not true, then skip the indented
statements.
Another fragment as an example:
if balance < 0:
transfer = -balance
backupAccount = backupAccount - transfer # take enough from the backup acct.
balance = balance + transfer
As with other kinds of statements with a heading and an indented block, the block can have more than one
statement. The assumption in the example above is that if an account goes negative, it is brought back to
0 by transferring money from a backup account in several steps.
93
3.1. IF STATEMENTS 94

In the examples above the choice is between doing something (if the condition is True) or nothing (if the
condition is False). Often there is a choice of two possibilities, only one of which will be done, depending
on the truth of a condition.

3.1.3. if-else Statements. Run the example program, clothes.py. Try it at least twice, with inputs:
50, 80. As you can see, you get different results, depending on the input. The main code of clothes.py is:
temperature = float(input(’What is the temperature? ’))
if temperature > 70:
print(’Wear shorts.’)
else:
print(’Wear long pants.’)
print(’Get some exercise outside.’)
The middle four lines are an if-else statement. Again it is close to English, though you might say “oth-
erwise” instead of “else” (but else is shorter!). There are two indented blocks: One, like in the simple if
statement, comes right after the if heading and is executed when the condition in the if heading is true. In
the if-else form this is followed by an else: line, followed by another indented block that is only executed
when the original condition is false. In an if-else statement exactly one of two possible indented blocks is
executed.
A line is also shown outdented next, about getting exercise. Since it is outdented, it is not a part of the
if-else statement: It is always executed in the normal forward flow of statements, after the if-else statement
(whichever block is selected).
The general Python syntax is
if condition :
indentedStatementBlockForTrueCondition
else:
indentedStatementBlockForFalseCondition
These statement blocks can have any number of statements, and can include about any kind of statement.

3.1.4. More Conditional Expressions. All the usual arithmetic comparisons may be made, but
many do not use standard mathematical symbolism, mostly for lack of proper keys on a standard keyboard.

Meaning Math Symbol Python Symbols


Less than < <
Greater than > >
Less than or equal ≤ <=
Greater than or equal ≥ >=
Equals = ==
Not equal 6= !=
There should not be space between the two-symbol Python substitutes.
Notice that the obvious choice for equals, a single equal sign, is not used to check for equality. An
annoying second equal sign is required. This is because the single equal sign is already used for assignment
in Python, so it is not available for tests. It is a common error to use only one equal sign when you mean to
test for equality, and not make an assignment!
Tests for equality do not make an assignment, and they do not require a variable on the left. Any
expressions can be tested for equality or inequality (!=). They do not need to be numbers! Predict the
results and try each line in the Shell:
x = 5
x
x == 5
x == 6
x
x != 6
x = 6
3.1. IF STATEMENTS 95

6 == x
6 != x
’hi’ == ’h’ + ’i’
’HI’ != ’hi’
[1, 2] != [2, 1]
An equality check does not make an assignment. Strings are case sensitive. Order matters in a list.
Try in the Shell:
’a’ > 5
When the comparison does not make sense, an Exception is caused.1
Exercise 3.1.4.1. Write a program, graduate.py, that prompts students for how many credits they
have. Print whether of not they have enough credits for graduation. (At Loyola University Chicago 128
credits are needed for graduation.)
Exercise 3.1.4.2. Following up on the discussion of the inexactness of float arithmetic in Section 1.14.3,
confirm that Python does not consider .1 + .2 to be equal to .3: Write a simple condition into the Shell to
test.
Here is another example: Pay with Overtime. Given a person’s work hours for the week and regular
hourly wage, calculate the total pay for the week, taking into account overtime. Hours worked over 40 are
overtime, paid at 1.5 times the normal rate. This is a natural place for a function enclosing the calculation.
Read the setup for the function:
def calcWeeklyWages(totalHours, hourlyWage):
’’’Return the total weekly wages for a worker working totalHours,
with a given regular hourlyWage. Include overtime for hours over 40.
’’’
The problem clearly indicates two cases: when no more than forty hours are worked or when more than
40 hours are worked. In case more than 40 hours are worked, it is convenient to introduce a variable
overtimeHours. You are encouraged to think about a solution before going on and examining mine.
You can try running my complete example program, wages.py, also shown below. The format operation
at the end of the main function uses the floating point format (Section 1.14.3) to show two decimal places
for the cents in the answer:
def calcWeeklyWages(totalHours, hourlyWage):
’’’Return the total weekly wages for a worker working totalHours,
with a given regular hourlyWage. Include overtime for hours over 40.
’’’

if totalHours <= 40:


totalWages = hourlyWage*totalHours
else:
overtime = totalHours - 40
totalWages = hourlyWage*40 + (1.5*hourlyWage)*overtime
return totalWages

def main():
hours = float(input(’Enter hours worked: ’))
wage = float(input(’Enter dollars paid per hour: ’))
total = calcWeeklyWages(hours, wage)
print(’Wages are ${total:.2f}.’.format(**locals()))

main()
Here the input was intended to be numeric, but it could be decimal so the conversion from string was via
float, not int.
1This is an improvement that is new in Python 3.0
3.1. IF STATEMENTS 96

Below is an equivalent alternative version of the body of calcWeeklyWages, used in wages1.py. It uses
just one general calculation formula and sets the parameters for the formula in the if statement. There are
generally a number of ways you might solve the same problem!
if totalHours <= 40:
regularHours = totalHours
overtime = 0
else:
overtime = totalHours - 40
regularHours = 40
return hourlyWage*regularHours + (1.5*hourlyWage)*overtime

3.1.5. Multiple Tests and if-elif Statements . Often you want to distinguish between more than
two distinct cases, but conditions only have two possible results, True or False, so the only direct choice is
between two options. As anyone who has played “20 Questions” knows, you can distinguish more cases by
further questions. If there are more than two choices, a single test may only reduce the possibilities, but
further tests can reduce the possibilities further and further. Since most any kind of statement can be placed
in an indented statement block, one choice is a further if statement. For instance consider a function to
convert a numerical grade to a letter grade, ’A’, ’B’, ’C’, ’D’ or ’F’, where the cutoffs for ’A’, ’B’, ’C’, and
’D’ are 90, 80, 70, and 60 respectively. One way to write the function would be test for one grade at a time,
and resolve all the remaining possibilities inside the next else clause:
def letterGrade(score):
if score >= 90:
letter = ’A’
else: # grade must be B, C, D or F
if score >= 80:
letter = ’B’
else: # grade must be C, D or F
if score >= 70:
letter = ’C’
else: # grade must D or F
if score >= 60:
letter = ’D’
else:
letter = ’F’
return letter
This repeatedly increasing indentation with an if statement as the else block can be annoying and dis-
tracting. A preferred alternative in this situation, that avoids all this indentation, is to combine each else
and if block into an elif block:
def letterGrade(score):
if score >= 90:
letter = ’A’
elif score >= 80:
letter = ’B’
elif score >= 70:
letter = ’C’
elif score >= 60:
letter = ’D’
else:
letter = ’F’
return letter
The most elaborate syntax for an if statement, if-elif-...-else is indicated in general below:
if condition1 :
3.1. IF STATEMENTS 97

indentedStatementBlockForTrueCondition1
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.
Be careful of the strange Python contraction. It is elif, not elseif. A program testing the letterGrade
function is in example program grade1.py.
Exercise 3.1.5.1. In Idle, load grade1.py and save it as grade2.py Modify grade2.py so it has an
equivalent version of the letterGrade function that tests in the opposite order, first for F, then D, C, ....
Hint: How many tests do you need to do? 2 Be sure to run your new version and test with different inputs
that test all the different paths through the program.
Exercise 3.1.5.2. Write a program sign.py to ask the user for a number. Print out which category
the number is in: ’positive’, ’negative’, or ’zero’.
Exercise 3.1.5.3. Modify the wages.py or the wages1.py example to create a program wages2.py that
assumes people are paid double time for hours over 60. Hence they get paid for at most 20 hours overtime
at 1.5 times the normal rate. For example, a person working 65 hours with a regular wage of $10 per hour
would work at $10 per hour for 40 hours, at 1.5*$10 for 20 hours of overtime, and 2*$10 for 5 hours of double
time, for a total of 10*40 + (1.5*10)*20 + (2*10)*5 = $800. You may find wages1.py easier to adapt than
wages.py.
A final alternative for if statements: if-elif-.... with no else. This would mean changing the syntax
for if-elif-else above so the final else: and the block after it would be omitted. It is similar to the basic if
statement without an else, in that it is possible for no indented block to be executed. This happens if none
of the conditions in the tests are true.
With an else included, exactly one of the indented blocks is executed. Without an else, at most one
of the indented blocks is executed.
if weight > 120:
print(’Sorry, we can not take a suitcase that heavy.’)
elif weight > 50:
print(’There is a $25 charge for luggage that heavy.’)

This if-elif statement only prints a line if there is a problem with the weight of the suitcase.
3.1.6. Nesting Control-Flow Statements, Part I. The power of a language like Python comes
largely from the variety of ways basic statements can be combined. In particular, for and if statements
can be nested inside each other’s indented blocks. For example, suppose you want to print only the positive
numbers from an arbitrary list of numbers in a function with the following heading. Read the pieces for now.
def printAllPositive(numberList):
’’’Print only the positive numbers in numberList.’’’
For example, suppose numberList is [3, -5, 2, -1, 0, 7]. As a human, who has eyes of amazing capacity,
you are drawn immediately to the actual correct numbers, 3, 2, and 7, but clearly a computer doing this
systematically will have to check every number. That easily suggests a for-each loop starting
for num in numberList:
24 tests to distinguish the 5 cases, as in the previous version
3.1. IF STATEMENTS 98

What happens in the body of the loop is not the same thing each time: some get printed, and for those we
will want the statement
print(num)
but some do not get printed, so it may at first seem that this is not an appropriate situation for a for-each
loop, but in fact, there is a consistent action required: Every number must be tested to see if it should be
printed. This suggests an if statement, with the condition num > 0. Try loading into Idle and running the
example program onlyPositive.py, whose code is shown below. It ends with a line testing the function:
def printAllPositive(numberList):
’’’Print only the positive numbers in numberList.’’’
for num in numberList:
if num > 0:
print(num)

printAllPositive([3, -5, 2, -1, 0, 7])


This idea of nesting if statements enormously expands the possibilities with loops. Now different things
can be done at different times in loops, as long as there is a consistent test to allow a choice between the
alternatives.
The rest of this section deals with graphical examples.
Run example program bounce1.py. It has a red ball moving and bouncing obliquely off the edges. If you
watch several times, you should see that it starts from random locations. Also you can repeat the program
from the Shell prompt after you have run the script. For instance, right after running the program, try in
the Shell
bounceBall(-3, 1)
The parameters give the amount the shape moves in each animation step. You can try other values in the
Shell, preferably with magnitudes less than 10.
For the remainder of the description of this example, read the extracted text pieces.
The animations before this were totally scripted, saying exactly how many moves in which direction, but
in this case the direction of motion changes with every bounce. The program has a graphic object shape
and the central animation step is
shape.move(dx, dy)
but in this case, dx and dy have to change when the ball gets to a boundary. For instance, imagine the ball
getting to the left side as it is moving to the left and up. The bounce obviously alters the horizontal part of
the motion, in fact reversing it, but the ball would still continue up. The reversal of the horizontal part of
the motion means that the horizontal shift changes direction and therefore its sign:
dx = -dx
but dy does not need to change. This switch does not happen at each animation step, but only when the
ball reaches the edge of the window. It happens only some of the time – suggesting an if statement. Still
the condition must be determined. Suppose the center of the ball has coordinates (x, y). When x reaches
some particular x coordinate, call it xLow, the ball should bounce.
The edge of the window is at coordinate 0, but xLow should not be 0, or the ball would be half way off
the screen before bouncing! For the edge of the ball to hit the edge of the screen, the x coordinate of the
center must be the length of the radius away, so actually xLow is the radius of the ball.
Animation goes quickly in small steps, so I cheat. I allow the ball to take one (small, quick) step past
where it really should go (xLow), and then we reverse it so it comes back to where it belongs. In particular
if x < xLow:
dx = -dx
There are similar bounding variables xHigh, yLow and yHigh, all the radius away from the actual edge
coordinates, and similar conditions to test for a bounce off each possible edge. Note that whichever edge is
hit, one coordinate, either dx or dy, reverses. One way the collection of tests could be written is
if x < xLow:
dx = -dx
if x > xHigh
3.1. IF STATEMENTS 99

dx = -dx
if y < yLow:
dy = -dy
if y > yHigh
dy = -dy
This approach would cause there to be some extra testing: If it is true that x < xLow, then it is impossible
for it to be true that x > xHigh, so we do not need both tests together. We avoid unnecessary tests with
an elif clause (for both x and y):
if x < xLow:
dx = -dx
elif x > xHigh
dx = -dx
if y < yLow:
dy = -dy
elif y > yHigh
dy = -dy
Note that the middle if is not changed to an elif, because it is possible for the ball to reach a corner, and
need both dx and dy reversed.
The program also uses several accessor methods for graphics objects that we have not used in examples
yet. Various graphics objects, like the circle we are using as the shape, know their center point, and it can
be accessed with the getCenter() method. (Actually a clone of the point is returned.) Also each coordinate
of a Point can be accessed with the getX() and getY() methods.
This explains the new features in the central function defined for bouncing around in a box, bounceInBox.
The animation arbitrarily goes on in a simple repeat loop for 600 steps. (A later example will improve this
behavior.):
def bounceInBox(shape, dx, dy, xLow, xHigh, yLow, yHigh):
’’’ Animate a shape moving in jumps (dx, dy), bouncing when
its center reaches the low and high x and y coordinates.
’’’

delay = .005
for i in range(600):
shape.move(dx, dy)
center = shape.getCenter()
x = center.getX()
y = center.getY()
if x < xLow:
dx = -dx
elif x > xHigh:
dx = -dx

if y < yLow:
dy = -dy
elif y > yHigh:
dy = -dy
time.sleep(delay)
The program starts the ball from an arbitrary point inside the allowable rectangular bounds. This is
encapsulated in a utility function included in the program, getRandomPoint. The getRandomPoint function
uses the randrange function from the module random. Note that in parameters for both the functions range
and randrange, the end stated is past the last value actually desired:
def getRandomPoint(xLow, xHigh, yLow, yHigh):
’’’Return a random Point with coordinates in the range specified.’’’
x = random.randrange(xLow, xHigh+1)
3.1. IF STATEMENTS 100

y = random.randrange(yLow, yHigh+1)
return Point(x, y)
The full program is listed below, repeating bounceInBox and getRandomPoint for completeness. Several
parts that may be useful later, or are easiest to follow as a unit, are separated out as functions. Make sure
you see how it all hangs together or ask questions!
’’’
Show a ball bouncing off the sides of the window.
’’’
from graphics import *
import time, random

def bounceInBox(shape, dx, dy, xLow, xHigh, yLow, yHigh):


’’’ Animate a shape moving in jumps (dx, dy), bouncing when
its center reaches the low and high x and y coordinates.
’’’

delay = .005
for i in range(600):
shape.move(dx, dy)
center = shape.getCenter()
x = center.getX()
y = center.getY()
if x < xLow:
dx = -dx
elif x > xHigh:
dx = -dx

if y < yLow:
dy = -dy
elif y > yHigh:
dy = -dy
time.sleep(delay)

def getRandomPoint(xLow, xHigh, yLow, yHigh):


’’’Return a random Point with coordinates in the range specified.’’’
x = random.randrange(xLow, xHigh+1)
y = random.randrange(yLow, yHigh+1)
return Point(x, y)

def makeDisk(center, radius, win):


’’’return a red disk that is drawn in win with given center and radius.’’’

disk = Circle(center, radius)


disk.setOutline("red")
disk.setFill("red")
disk.draw(win)
return disk

def bounceBall(dx, dy):


’’’Make a ball bounce around the screen, initially moving by (dx, dy)
at each jump.’’’

winWidth = 290
winHeight = 290
3.1. IF STATEMENTS 101

win = GraphWin(’Ball Bounce’, winWidth, winHeight)


win.setCoords(0, 0, winWidth, winHeight)

radius = 10
xLow = radius # center is separated from the wall by the radius at a bounce
xHigh = winWidth - radius
yLow = radius
yHigh = winHeight - radius

center = getRandomPoint(xLow, xHigh, yLow, yHigh)


ball = makeDisk(center, radius, win)

bounceInBox(ball, dx, dy, xLow, xHigh, yLow, yHigh)


win.close()

bounceBall(3, 5)

3.1.7. Compound Boolean Expressions. To be eligible to graduate from Loyola University Chicago,
you must have 128 units of credit and a GPA of at least 2.0. This translates directly into Python as a
compound condition:
units >= 128 and GPA >=2.0

This is true if both units >= 128 is true and GPA >=2.0 is true. A short example program using this would
be:
units = input(’How many units of credit do you have? ’)
GPA = input(’What is your GPA? ’)
if units >= 128 and GPA >=2.0:
print(’You are eligible to graduate!’)
else:
print(’You are not eligible to graduate.’)
The new Python syntax is for the operator and:
condition1 and condition2
It is true if both of the conditions are true. It is false if at least one of the conditions is false.
Exercise 3.1.7.1. A person is eligible to be a US Senator who is at least 30 years old and has been
a US citizen for at least 9 years. Write a version of a program congress.py to obtain age and length of
citizenship from the user and print out if a person is eligible to be a Senator or not. A person is eligible to
be a US Representative who is at least 25 years old and has been a US citizen for at least 7 years. Elaborate
your program congress.py so it obtains age and length of citizenship and prints whether a person is eligible
to be a US Representative only, or is eligible for both offices, or is eligible for neither.
In the last example in the previous section, there was an if-elif statement where both tests had the same
block to be done if the condition was true:
if x < xLow:
dx = -dx
elif x > xHigh:
dx = -dx
There is a simpler way to state this in a sentence: If x < xLow or x > xHigh, switch the sign of dx. That
translates directly into Python:
if x < xLow or x > xHigh:
dx = -dx
The word or makes another compound condition:
condition1 or condition2
3.1. IF STATEMENTS 102

is true if at least one of the conditions is true. It is false if both conditions are false. This corresponds to one
way the word “or” is used in English. Other times in English “or” is used to mean exactly one alternative is
true.
It is often convenient to encapsulate complicated tests inside a function. Think how to complete the
function starting:
def isInside(rect, point):
’’’Return True if the point is inside the Rectangle rect.’’’

pt1 = rect.getP1()
pt2 = rect.getP2()
Recall that a Rectangle is specified in its constructor by two diagonally oppose Points. This example gives
the first use in the tutorials of the Rectangle methods that recover those two corner points, getP1 and
getP2. The program calls the points obtained this way p1 and p2. The x and y coordinates of pt1, pt2,
and point can be recovered with the methods of the Point type, getX() and getY().
Suppose that I introduce variables for the x coordinates of p1, point, and p2, calling these x-coordinates
end1, val, and end2, respectively. On first try you might decide that the needed mathematical relationship
to test is
end1 <= val <= end2
Unfortunately, this is not enough: The only requirement for the two corner points is that they be diagonally
opposite, not that the coordinates of the second point are higher than the corresponding coordinates of the
first point. It could be that end1 is 200; end2 is 100, and val is 120. In this latter case val is between end1
and end2, but substituting into the expression above
200 <= 120 <= 100
is False. The 100 and 200 need to be reversed in this case. This makes a complicated situation, and an issue
which must be revisited for both the x and y coordinates. I introduce an auxiliary function isBetween to
deal with one coordinate at a time. It starts:
def isBetween(val, end1, end2):
’’’Return True if val is between the ends.
The ends do not need to be in increasing order.’’’
Clearly this is true if the original expression, end1 <= val <= end2, is true. You must also consider the
possible case when the order of the ends is reversed: end2 <= val <= end1. How do we combine these two
possibilities? The Boolean connectives to consider are and and or. Which applies? You only need one to be
true, so or is the proper connective:
A correct but redundant function body would be:
if end1 <= val <= end2 or end2 <= val <= end1:
return True
else:
return False
Check the meaning: if the compound expression is True, return True. If the condition is False, return False
– in either case return the same value as the test condition. See that a much simpler and neater version is
to just return the value of the condition itself!
return end1 <= val <= end2 or end2 <= val <= end1
In general you should not need an if-else statement to choose between true and false values!
A side comment on expressions like
end1 <= val <= end2
Other than the two-character operators, this is like standard math syntax, chaining comparisons. In Python
any number of comparisons can be chained in this way, closely approximating mathematical notation.
Though this is good Python, be aware that if you try other high-level languages like Java and C++, such
an expression is gibberish. Another way the expression can be expressed (and which translates directly to
other languages) is:
end1 <= val and val <= end2
3.1. IF STATEMENTS 103

So much for the auxiliary function isBetween. Back to the isInside function. You can use the isBetween
function to check the x coordinates, isBetween(point.getX(), p1.getX(), p2.getX()), and to check the
y coordinates, isBetween(point.getY(), p1.getY(), p2.getY()). Again the question arises: how do
you combine the two tests?
In this case we need the point to be both between the sides and between the top and bottom, so the
proper connector is and.
Think how to finish the isInside method. Hint: 3
Sometimes you want to test the opposite of a condition. As in English you can use the word not. For
instance, to test if a Point was not inside Rectangle Rect, you could use the condition
not isInside(rect, point)
In general,
not condition
is True when condition is False, and False when condition is True.
The example program chooseButton1.py, shown below, is a complete program using the isInside func-
tion in a simple application, choosing colors. Pardon the length. Do check it out. It will be the starting
point for a number of improvements that shorten it and make it more powerful in the next section. First a
brief overview:
The program includes the functions isBetween and isInside that have already been discussed. The
program creates a number of colored rectangles to use as buttons and also as picture components. Aside
from specific data values, the code to create each rectangle is the same, so the action is encapsulated in a
function, makeColoredRect. All of this is fine, and will be preserved in later versions.
The present main function is long, though. It has the usual graphics starting code, draws buttons and
picture elements, and then has a number of code sections prompting the user to choose a color for a picture
element. Each code section has a long if-elif-else test to see which button was clicked, and sets the color of
the picture element appropriately.
’’’Make a choice of colors via mouse clicks in Rectangles --
A demonstration of Boolean operators and Boolean functions.’’’

from graphics import *

def isBetween(x, end1, end2): #same as before


’’’Return True if x is between the ends or equal to either.
The ends do not need to be in increasing order.’’’

return end1 <= x <= end2 or end2 <= x <= end1

def isInside(point, rect):


’’’Return True if the point is inside the Rectangle rect.’’’

pt1 = rect.getP1()
pt2 = rect.getP2()
return isBetween(point.getX(), pt1.getX(), pt2.getX()) and \
isBetween(point.getY(), pt1.getY(), pt2.getY())

def makeColoredRect(corner, width, height, color, win):


’’’ Return a Rectangle drawn in win with the upper left corner
and color specified.’’’

corner2 = corner.clone()
corner2.move(width, -height)
rect = Rectangle(corner, corner2)
rect.setFill(color)
3Once again, you are calculating and returning a Boolean result. You do not need an if-else statement.
3.1. IF STATEMENTS 104

rect.draw(win)
return rect

def main():
winWidth = 400
winHeight = 400
win = GraphWin(’pick Colors’, winWidth, winHeight)
win.setCoords(0, 0, winWidth, winHeight)

redButton = makeColoredRect(Point(310, 350), 80, 30, ’red’, win)


yellowButton = makeColoredRect(Point(310, 310), 80, 30, ’yellow’, win)
blueButton = makeColoredRect(Point(310, 270), 80, 30, ’blue’, win)

house = makeColoredRect(Point(60, 200), 180, 150, ’gray’, win)


door = makeColoredRect(Point(90, 150), 40, 100, ’white’, win)
roof = Polygon(Point(50, 200), Point(250, 200), Point(150, 300))
roof.setFill(’black’)
roof.draw(win)

msg = Text(Point(winWidth/2, 375),’Click to choose a house color.’)


msg.draw(win)
pt = win.getMouse()
if isInside(pt, redButton):
color = ’red’
elif isInside(pt, yellowButton):
color = ’yellow’
elif isInside(pt, blueButton):
color = ’blue’
else :
color = ’white’
house.setFill(color)

msg.setText(’Click to choose a door color.’)


pt = win.getMouse()
if isInside(pt, redButton):
color = ’red’
elif isInside(pt, yellowButton):
color = ’yellow’
elif isInside(pt, blueButton):
color = ’blue’
else :
color = ’white’
door.setFill(color)

msg.setText(’Click anywhere to quit.’)


win.getMouse()
win.close()

main()

The only further new feature used is in the long return statement in isInside.

return isBetween(point.getX(), pt1.getX(), pt2.getX()) and \


isBetween(point.getY(), pt1.getY(), pt2.getY())
3.2. LOOPS AND TUPLES 105

Recall that Python is smart enough to realize that a statement continues to the next line if there is an
unmatched pair of parentheses or brackets. Above is another situation with a long statement, but there are
no unmatched parentheses on a line. For readability it is best not to make an enormous long line that would
run off your screen or paper. Continuing to the next line is recommended. You can make the final character
on a line be a backslash (\) to indicate the statement continues on the next line. This is not particularly
neat, but it is a rather rare situation. Most statements fit neatly on one line, and the creator of Python
decided it was best to make the syntax simple in the most common situation. (Many other languages require
a special statement terminator symbol like ’;’ and pay no attention to newlines).
The chooseButton1.py program is long partly because of repeated code. The next section gives another
version involving lists.

3.2. Loops and Tuples


This section will discuss several improvements to the chooseButton1.py program from the last section
that will turn it into example program chooseButton2.py.
First an introduction to tuples, which we will use for the first time in this section:
A tuple is similar to a list except that a literal tuple is enclosed in parentheses rather than square
brackets and a tuple is immutable. In particular you cannot change the length or substitute elements, unlike
a list. Examples are
(1, 2, 3)
(’yes’, ’no’)
They are another way to make several items into a single object. You can refer to individual parts with
indexing, like with lists, but a more common way is with multiple assignment. A silly simple example:
tup = (1, 2)
(x, y) = tup
print(x) # printe 1
print(y) # prints 2
Now back to improving the chooseButton1.py program, which has similar code repeating in several places.
Imagine how much worse it would be if there were more colors to choose from and more parts to color!
First consider the most egregious example:
if isInside(pt, redButton):
color = ’red’
elif isInside(pt, yellowButton):
color = ’yellow’
elif isInside(pt, blueButton):
color = ’blue’
else :
color = ’white’
Not only is this exact if statement repeated several times, all the conditions within the if statement are
very similar! Part of the reason I did not put this all in a function was the large number of separate variables.
On further inspection, the particular variables redButton, yellowButton, blueButton, all play a similar
role, and their names are not really important, it is their associations that are important: that redButton
goes with ’red’, .... When there is a sequence of things all treated similarly, it suggests a list and a loop. An
issue here is that the changing data is paired, a rectangle with a color string. There are a number of ways to
handle such associations. A very neat way in Python to package a pair (or more things together) is a tuple,
turning several things into one object, as in (redButtton, ’red’). Objects such are this tuple can be put in a
larger list:

choicePairs = [(redButtton, ’red’), (yellowButton, ’yellow’), (blueButton, ’blue’)]


Such tuples may be neatly handled in a for statement. You can imagine a function to encapsulate the color
choice starting:
def getChoice(choicePairs, default, win):
’’’Given a list choicePairs of tuples, with each tuple in the form
3.2. LOOPS AND TUPLES 106

(rectangle, choice), return the choice that goes with the rectangle
in win where the mouse gets clicked, or return default if the click
is in none of the rectangles.’’’

point = win.getMouse()
for (rectangle, choice) in choicePairs:
#....
This is the first time we have had a for-loop going through a list of tuples. Recall that we can do multiple
assignments at once with tuples. This also works in a for-loop heading. The for-loop goes through one
tuple in the list choicePairs at a time. The first time through the loop the tuple taken from the list
is (redButtton, ’red’). This for-loop does a multiple assignment to (rectangle, choice)each time
through the loop, so the first time rectangle refers to redButton and choice refers to ’red’. The next
time through the loop, the second tuple from the list is used, (yellowButton, ’yellow’)so this time inside
the loop rectangle will refer to yellowButton and choice refers to ’yellow’....This is a neat Python
feature.4
There is still a problem. We could test each rectangle in the for-each loop, but the original if-elif...
statement in chooseButton1.py stops when the first condition is true. For-each statements are designed to go
all the way through the sequence. There is a simple way out of this in a function: A return statement always
stops the execution of a function. When we have found the rectangle containing the point, the function can
return the desired choice immediately!
def getChoice(choicePairs, default, win):
’’’Given a list of tuples (rectangle, choice), return the choice
that goes with the rectangle in win where the mouse gets clicked,
or return default if the click is in none of the rectangles.’’’

point = win.getMouse()
for (rectangle, choice) in choicePairs:
if isInside(point, rectangle):
return choice
return default
Note that the else part in chooseButton1.py corresponds to the statement after the loop above. If execution
gets past the loop, then none of the conditions tested in the loop was true.
With appropriate parameters, the looping function is a complete replacement for the original if-elif
statement! The replacement has further advantages.
• There can be an arbitrarily long list of pairs, and the exact same code works.
• This code is clearer and easier to read, since there is no need to read through a long sequence of
similar if-elif clauses.
• The names of the rectangles in the tuples in the list are never referred to. They are unnecessary
here. Only a list needs to be specified. That could be useful earlier in the program ....
Are individual names for the rectangles needed earlier? No, the program only need to end up with the pairs
of the form (rectangle, color) in a list. The statements in the original program, below, have a similar form
which will allow them to be rewritten:
redButton = makeColoredRect(Point(310, 350), 80, 30, ’red’, win)
yellowButton = makeColoredRect(Point(310, 310), 80, 30, ’yellow’, win)
blueButton = makeColoredRect(Point(310, 270), 80, 30, ’blue’, win)
As stated earlier, we could use the statements above and then make a list of pairs with the statement

choicePairs = [(redButtton, ’red’), (yellowButton, ’yellow’), (blueButton, ’blue’)]

4Particularly in other object-oriented languages where lists and tuples are way less easy to use, the preferred way to group
associated objects, like rectangle and choice, is to make a custom object type containing them all. This is also possible and
often useful in Python. In some relatively simple cases, like in the current example, use of tuples can be easier to follow, though
the approach taken is a matter of taste. The topic of creating custom type of objects will not be taken up in these tutorials.
3.2. LOOPS AND TUPLES 107

Now I will look at an alternative that would be particularly useful if there were considerably more buttons
and colors.
All the assignment statements with makeColorRect have the same format, but differing data for several
parameters. I use that fact in the alternate code:
choicePairs = list()
buttonSetup = [(310, 350, ’red’), (310, 310, ’yellow’), (310, 270, ’blue’)]
for (x, y, color) in buttonSetup:
button = makeColoredRect(Point(x, y), 80, 30, color, win)
choicePairs.append((button, color))
I extract the changing data from the creation of the rectangles into a list, buttonSetup. Since more than one
data items are different for each of the original lines, the list contains a tuple of data from each of the original
lines. Then I loop through this list and not only create the rectangles for each color, but also accumulates
the (rectangle, color) pairs for the list choicePairs.
Note the double parentheses in the last line of the code. The outer ones are for the method call. The
inner ones create a single tuple as the parameter.
Assuming I do not need the original individual names of the Rectangles, this code with the loop will
completely substitute for the previous code with its separate lines with the separate named variables and
the recurring formats.
This code has advantages similar to those listed above for the getChoice code.
Now look at what this new code means for the interactive part of the program. The interactive code
directly reduces to
msg = Text(Point(winWidth/2, 375),’Click to choose a house color.’)
msg.draw(win)
color = getChoice(colorPairs, ’white’, win)
house.setFill(color)

msg.setText(’Click to choose a door color.’)


color = getChoice(colorPairs, ’white’, win)
door.setFill(color)
In the original version with the long if-elif statements, the interactive portion only included portions for
the user to set the color of two shapes in the picture (or you would have been reading code forever). Looking
now at the similarity of the code for the two parts, we can imagine another loop, that would easily allow for
many more parts to be colored interactively.
There are still several differences to resolve. First the message msg is created the first time, and only
the text is set the next time. That is easy to make consistent by splitting the first part into an initialization
and a separate call to setText like in the second part:
msg = Text(Point(winWidth/2, 375),’’)
msg.draw(win)

msg.setText(’Click to choose a house color.’)


Then look to see the differences between the code for the two choices. The shape object to be colored and
the name used to describe the shape change: two changes in each part. Again tuples can store the changes
of the form (shape, description). This is another place appropriate for a loop driven by tuples.. The (shape,
description) tuples should be explicitly written into a list that can be called shapePairs. We could easily
extend the list shapePairs to allow more graphics objects to be colored. In the code below, the roof is added.
The new interactive code can start with:
shapePairs = [(house, ’house’), (door, ’door’), (roof, ’roof’)]
msg = Text(Point(winWidth/2, 375),’’)
msg.draw(win)
for (shape, description) in shapePairs:
prompt = ’Click to choose a + description + ’ color.’
# ....
3.2. LOOPS AND TUPLES 108

Can you finish the body of the loop? Look at the original version of the interactive code. When you are
done thinking about it, go on to my solution. The entire code is in example program chooseButton2.py,
and also below. The changes from chooseButton1.py are in three blocks, each labeled #NEW in the code.
The new parts are the getChoice function and the two new sections of main with the loops:
’’’Make a choice of colors via mouse clicks in Rectangles --
Demonstrate loops using lists of tuples of data.’’’

from graphics import *

def isBetween(x, end1, end2):


’’’Return True if x is between the ends or equal to either.
The ends do not need to be in increasing order.’’’

return end1 <= x <= end2 or end2 <= x <= end1

def isInside(point, rect):


’’’Return True if the point is inside the Rectangle rect.’’’

pt1 = rect.getP1()
pt2 = rect.getP2()
return isBetween(point.getX(), pt1.getX(), pt2.getX()) and \
isBetween(point.getY(), pt1.getY(), pt2.getY())

def makeColoredRect(corner, width, height, color, win):


’’’ Return a Rectangle drawn in win with the upper left corner
and color specified.’’’

corner2 = corner.clone()
corner2.move(width, -height)
rect = Rectangle(corner, corner2)
rect.setFill(color)
rect.draw(win)
return rect

def getChoice(choicePairs, default, win): #NEW, discussed above


’’’Given a list of tuples (rectangle, choice), return the choice
that goes with the rectangle in win where the mouse gets clicked,
or return default if the click is in none of the rectangles.’’’

point = win.getMouse()
for (rectangle, choice) in choicePairs:
if isInside(point, rectangle):
return choice
return default

def main():
winWidth = 400
winHeight = 400
win = GraphWin(’Pick Colors’, winWidth, winHeight)
win.setCoords(0, 0, winWidth, winHeight)

#NEW, shown in the discussion above


choicePairs = list() # elements (button rectangle, color)
buttonSetup = [(310, 350, ’red’), (310, 310, ’yellow’), (310, 270, ’blue’)]
3.3. WHILE STATEMENTS 109

for (x, y, color) in buttonSetup:


button = makeColoredRect(Point(x, y), 80, 30, color, win)
choicePairs.append((button, color))

house = makeColoredRect(Point(60, 200), 180, 150, ’gray’, win)


door = makeColoredRect(Point(90, 150), 40, 100, ’white’, win)
roof = Polygon(Point(50, 200), Point(250, 200), Point(150, 300))
roof.setFill(’black’)
roof.draw(win)

#NEW started in the discussion above.


shapePairs = [(house, ’house’), (door, ’door’), (roof, ’roof’)]
msg = Text(Point(winWidth/2, 375),’’)
msg.draw(win)
for (shape, description) in shapePairs:
prompt = ’Click to choose a + description + ’ color.’
msg.setText(prompt)
color = getChoice(choicePairs, ’white’, win)
shape.setFill(color)

msg.setText(’Click anywhere to quit.’)


win.getMouse()
win.close()

main()
Run it.
With the limited number of choices in chooseButton1.py, the change in length to convert to chooseBut-
ton2.py is not significant, but the change in organization is significant if you try to extend the program, as
in the exercise below. See if you agree!
Exercise 3.2.0.2. a. Write a program chooseButton3.py, modifying chooseButton2.py. Look at the
format of the list buttonSetup, and extend it so there is a larger choice of buttons and colors. Add at least
one button and color.
b. Further extend the program chooseButton3.py by adding some further graphical object shape to
the picture, and extend the list shapePairs, so they can all be interactively colored.
c. (Optional) If you would like to carry this further, also add a prompt to change the outline color of
each shape, and then carry out the changes the user desires.
d. (Optional Challenge) Look at the pattern within the list buttonSetup. It has a consistent x coordi-
nate, and there is a regular pattern to the change in the y coordinate (a consistent decrease each time). The
only data that is arbitrary each time is the sequence of colors. Write a further version chooseButton4.py
with a function makeButtonSetup, that takes a list of color names as a parameter and uses a loop to create
the list used as buttonSetup. End by returning this list. Use the function to initialize buttonSetup. If you
like, make the function more general and include parameters for the x coordinate, the starting y coordinate
and the regular y coordinate change.

3.3. While Statements


3.3.1. Simple while Loops. Other than the trick with using a return statement inside of a for loop,
all of the loops so far have gone all the way through a specified list. In any case the for loop has required the
use of a specific list. This is often too restrictive. A Python while loop behaves quite similarly to common
English usage. If I say
While your tea is too hot, add a chip of ice.
Presumably you would test your tea. If it were too hot, you would add a little ice. If you test again and it
is still too hot, you would add ice again. As long as you tested and found it was true that your tea was too
hot, you would go back and add more ice. Python has a similar syntax:
3.3. WHILE STATEMENTS 110

while condition :
indentedBlock
Setting up the English example in a similar format would be:
while your tea is too hot :
add a chip of ice
To make things concrete and numerical, suppose the following: The tea starts at 115 degrees Fahrenheit.
You want it at 112 degrees. A chip of ice turns out to lower the temperature one degree each time. You test
the temperature each time, and also print out the temperature before reducing the temperature. In Python
you could write and run the code below, saved in example program cool.py:
temperature = 115 #1
while temperature > 112: #2
print(temperature) #3
temperature = temperature - 1 #4
print(’The tea is cool enough.’) #5
I added a final line after the while loop to remind you that execution follows sequentially after a loop
completes.
If you play computer and follow the path of execution, you could generate the following table. Remem-
ber, that each time you reach the end of the indented block after the while heading, execution returns to
the while heading:

line temperature comment


1 115
2 115 > 112 is true, do loop
3 prints 115
4 114 115 - 1 is 114, loop back
2 114 > 112 is true, do loop
3 prints 114
4 113 114 - 1 is 113, loop back
2 113 > 112 is true, do loop
3 prints 113
4 112 113 - 1 is 112, loop back
2 112 > 112 is false, skip loop
5 prints that the tea is cool
Each time the end of the indented loop body is reached, execution returns to the while loop heading
for another test. When the test is finally false, execution jumps past the indented body of the while loop
to the next sequential statement.
A while loop generally follows the pattern of the successive modification loop introduced with for-each
loops:
initialization
while continuationCondition:
do main action to be repeated
prepare variables for the next time through the loop
Test yourself: Figure out by following the code, what is printed?
i = 4
while (i < 9):
print(i)
i = i+2
Check yourself by running the example program testWhile.py.
In Python, while is not used quite like in English. In English you could mean to stop as soon as the
condition you want to test becomes false. In Python the test is only made when execution for the loop starts,
3.3. WHILE STATEMENTS 111

not in the middle of the loop. Predict what will happen with this slight variation on the previous example,
switching the order in the loop body. Follow it carefully, one step at a time.
i = 4 #1
while (i < 9): #2
i = i+2 #3
print(i) #4
Check yourself by running the example program testWhile2.py.
The sequence order is important. The variable i is increased before it is printed, so the first number
printed is 6. Another common error is to assume that 10 will not be printed, since 10 is past 9, but the
test that may stop the loop is not made in the middle of the loop. Once the body of the loop is started, it
continues to the end, even when i becomes 10.

line i comment
1 4
2 4 < 9 is true, do loop
3 6 4+2=6
4 print 6
2 6 < 9 is true, do loop
3 8 6+2= 8
4 print 8
2 8 < 9 is true, do loop
3 10 8+2=10
4 112 print 10
2 10 < 9 is false, skip loop
Predict what happens in this related little program:
nums = list()
i = 4
while (i < 9):
nums.append(i)
i = i+2
print(nums)
Check yourself by running the example program testWhile3.py.

3.3.2. The range Function, In General. There is actually a much simpler way to generate the
previous sequence, using a further variation of the range function. Enter these lines separately in the Shell.
As in the simpler applications of range, the values are only generated one at a time, as needed. To see the
entire sequence at once, convert the sequence to a list:
nums = list(range(4, 9, 2))
print(nums)
The third parameter is needed when the step size from one element to the next is not 1.
The most general syntax is
range(start, pastEnd, step)
The value of the second parameter is always past the final element of the list. Each element after the first
in the list is step more than the previous one. Predict and try in the Shell:
list(range(4, 10, 2))
Actually the range function is even more sophisticated than indicated by the while loop above. The step
size can be negative.
Try in the Shell:
list(range(10, 0, -1))
Do you see how 0 is past the end of the list?
3.3. WHILE STATEMENTS 112

Make up a range function call to generate the list of temperatures printed in the tea example, 115,
114, 113. Test it in the Shell.

3.3.3. Interactive while Loops. The earlier examples of while loops were chosen for their simplicity.
Obviously they could have been rewritten with range function calls. Now lets try a more interesting example.
Suppose you want to let a user enter a sequence of lines of text, and want to remember each line in a list.
This could easily be done with a simple repeat loop if you knew the number of lines to enter. For example
if you want three lines:
lines = list()
print(’Enter 3 lines of text’)
for i in range(3):
line = input(’Next line: ’)
lines.append(line)

print(’Your lines were:’) # check now


for line in lines:
print(line)
The user may want to enter a bunch of lines and not count them all ahead of time. This means the number of
repetitions would not be known ahead of time. A while loop is appropriate here. There is still the question
of how to test whether the user wants to continue. An obvious but verbose way to do this is to ask before
every line if the user wants to continue, as shown below and in the example file readLines1.py. Read it
and then run it:
lines = list()
testAnswer = input(’Press y if you want to enter more lines: ’)
while testAnswer == ’y’:
line = input(’Next line: ’)
lines.append(line)
testAnswer = input(’Press y if you want to enter more lines: ’)

print(’Your lines were:’)


for line in lines:
print(line)
The data must be initialized before the loop, in order for the first test of the while condition to work. Also
the test must work when you loop back from the end of the loop body. This means the data for the test
must also be set up a second time, in the loop body.
The readLines1.py code works, but it may be more annoying than counting ahead! Two lines must be
entered for every one you actually want! A practical alternative is to use a sentinel: a piece of data that
would not make sense in the regular sequence, and which is used to indicate the end of the input. You could
agree to use the line DONE! Even simpler: if you assume all the real lines of data will actually have some
text on them, use an empty line as a sentinel. (If you think about it, the Python Shell uses this approach
when you enter a statement with an indented body.) This way you only need to enter one extra (very simple)
line, no matter how many lines of real data you have.
What should the while condition be now? Since the sentinel is an empty line, you might think line ==
’’, but that is the termination condition, not the continuation condition: You need the opposite condition.
To negate a condition in Python, you may use not, like in English,
not line == ’’
. Of course in this situation there is a shorter way, line != ’’. Run the example program readLines2.py,
shown below:
lines = list()
print(’Enter lines of text.’)
print(’Enter an empty line to quit.’)
line = input(’Next line: ’)
while line != ’’:

You might also like