Py Tutorial 57 84
Py Tutorial 57 84
2.1.4. Index Variables. All the concrete examples in the last two sections used literal numbers for
the indices. That is fine for learning the idea, but in practice, variables or expressions are almost always
used for indices. As usual the variable or expression is evaluated before being used. Try in Idle and see that
the example program index1.py makes sense:
s = ’word’
print(’The full string is: ’, s)
n = len(s)
for i in range(n):
print()
print(’i =’, i)
print(’The letter at index i:’, s[i])
print(’The part before index i (if any):’, s[:i])
print(’The part before index i+2:’, s[:i+2])
We will use index variables in more practical situations as we explain more operations with strings.
2.1.5. split. Syntax options for the split method with a string s:
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.
For example, read and follow:
>>> tale = ’This is the best of times.’
>>> tale.split()
[’This’, ’is’, ’the’, ’best’, ’of’, ’times.’]
>>> s = ’Mississippi’
>>> s.split(’i’)
[’M’, ’ss’, ’ss’, ’pp’, ’’]
>>> s.split() # no white space
[’Mississippi’]
Predict and test each line in the Shell:
line = ’Go: Tear some strings apart!’
seq = line.split()
seq
line.split(’:’)
line.split(’ar’)
lines = ’This includes\nsome new\nlines.’
lines.split()
2.1. STRINGS, PART III 58
2.1.6. join. Join is roughly the reverse of split. It joins together a sequence of strings. The syntax is
rather different. The separator sep comes first, since it has the right type (a string).
Syntax for the join method:
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.
For example (continuing in the Shell from the previous section, using seq):
>>> ’ ’.join(seq)
’Go: Tear some strings apart!’
>>> ’’.join(seq)
’Go:Tearsomestringsapart!’
>>> ’//’.join(seq)
’Go://Tear//some//strings//apart!’
Predict and try each line, continuing in the Shell:
’##’.join(seq)
’:’.join([’one’, ’two’, ’three’])
The methods split and join are often used in sequence:
Exercise 2.1.6.1. * Write a program underscores.py that would input a phrase from the user and
print out the phrase with the white space between words replaced by an underscore. For instance if the input
is "the best one", then it would print "the_best_one". The conversion can be done in one or two statements
using the recent string methods.
Exercise 2.1.6.2. ** An acronym is a string of capital letters formed by taking the first letters from
a phrase. For example, SADD is an acronym for ’students against drunk driving’. Note that the acronym
should be composed of all capital letters even if the original words are not. Write a program acronym.py
that has the user input a phrase and then prints the corresponding acronym.
To get you started, here are some things you will need to do. First check that you understand the basic
syntax to accomplish the different individual tasks: Indicate the proper syntax using a Python function or
operation will allow you to accomplish each task. Invent appropriate variable names for the different parts.
This is not complete instructions! the idea is to make sure you know the basic syntax to use in all these
situations. See the questions after the list to help you put together the final program.
(1) What type of data will the input be? What type of data will the output be?
(2) Get the phrase from the user.
(3) Convert to upper case.
(4) Divide the phrase into words.
(5) Initialize a new empty list, letters.
(6) Get the first letter of each word.
(7) Append the first letter to the list letters.
(8) Join the letters together, with no space between them.
(9) Print the acronym.
Which of these steps is in a loop? What for statement controls this loop?
Put these ideas together and write and test your program acronym.py. Make sure you use names for
the objects that are consistent from one line to the next! (You might not have done that when you first
considered the syntax and ideas needed for 1-9 above individually.)
2.1.7. Further Exploration: As the dir(’’) list showed, there are many more operations on strings
than we have discussed, and there are further variations of the ones above with more parameters. If you want
to reach a systematic reference from inside Idle, go to Help-> Python Docs, select Library Reference, and
Section 2.3 Built-in Types, and then Section 2.3.6.1, String Methods. (This depends on you being attached
to the Internet, or having idle configured to look at a local copy of the official Python documentation.) Many
methods use features we have not discussed yet, but currently accessible methods are capitalize, title,
strip, rfind, replace....
2.2. MORE CLASSES AND METHODS 59
2.2.1. Appending to a List. Before making a version of the madlib program that is much easier to
use with new stories, we need a couple of facts about other types of objects that are built into Python.
So far we have used lists, but we have not changed the contents of lists. The most obvious way to change
a list is to add a new element onto the end. Lists have the method append. It modifies the original list.
Another word for modifiable is mutable. Lists are mutable. Most of the types of object considered so far
(int, str, float) are immutable or not mutable. Read and see how the list named words changes:
>>> words = list()
>>> words
[]
>>> words.append(’animal’)
>>> words
[’animal’]
>>> words.append(’food’)
>>> words
[’animal’, ’food’]
>>> words.append(’city’)
>>> words
[’animal’, ’food’, ’city’]
This is particularly useful in a loop, where we can accumulate a new list. Read the start of this simple
example:
def multipleAll(numList, multiplier):
’’’Return a new list containing all of the elements of numList,
each multiplied by multiplier. For example:
Test yourself : If we are going to accumulate a list. How do we initialize the list?
In earlier versions of the accumulation loop, we needed an assignment statement to change the object
doing the accumulating, but now the method append modifies its list automatically, so we do not need an
assignment statement. Read and try the example program multiply.py:
def multipleAll(numList, multiplier): #1
’’’Return a new list containing all of the elements of numList,
each multiplied by multiplier. For example:
newList = list() #2
for num in numList: #3
newList.append(num*multiplier) #4
return newList #5
2.2. MORE CLASSES AND METHODS 60
1
2
3
5
Predict the result of the following, and then paste it into the Shell and test. (Technically, a set is unordered,
so you may not guess Python’s order, but see if you can get the right length and the right elements in some
order.)
set([’animal’, ’food’, ’animal’, ’food’, ’food’, ’city’])
2.2.3. Constructors. We have now seen several examples of the name of a type being used as a
function. Read these earlier examples:
x = int(’123’)
s = str(123)
4There is also a concise syntax called list comprehension that allows you to derive a new list from a given sequence. In
the example above, we could describe what happens in English as “make newList contain twice each number in numList”. This
is quite directly translated into an assignment with a list comprehension:
newList = [2*num for num in numList]
This is a lot like mathematical set definition notation, except without Greek symbols. List comprehensions also have fancier
options, but they are not covered in this tutorial.
2.3. MAD LIBS REVISITED 61
nums = list()
aSet = set(numberList)
In all such cases a new object of the specified type is constructed and returned, and such functions are called
constructors.
The first key is ’animal’ at formatString[6:12]. The next key is ’food’ at formatString[25:29]. To
identify each key as part of formatString we need not only the variable formatString, but also index
variables to locate the start and end of the slices. Obvious names for the indices are start and end. We
want to keep them current so the next key slice will always be
key = formatString[start : end]
Let us now put this all in an overall plan. We will have to continuously modify the start and end indices,
the key, and the list. We have a basic pattern for accumulating a list, involving initializing it and appending
to it. We can organize a plan, partly fleshed out, with a couple of approximations to be worked out still.
The parts that are not yet in Python are emphasized:
def getKeys(formatString):
keyList = list()
?? other initializations ??
repetitions = formatString.count(’{’)
for i in range(repetitions):
find the start and end of the next key
key = formatString[start : end]
keyList.append(key)
return keyList
We can see that the main piece left is to find the start and end indices for each key. The important word is
find: the method we consider is find. As with the plan for using count above, the beginnings of keys are
identified by the specific string ’{’. We can look first at
formatString.find(’{’)
but that is not the full solution. If we look at our concrete example, the value returned is 5, not 6. How in
general would we locate the beginning of the slice we want?
We do not want the position of the beginning of ’{’, but the position just after the ’{’. Since the
length of ’{’ is 1, the correct position is 5+1 = 6. We can generalize this to
start = formatString.find(’{’) + 1
OK, what about end? Clearly it is at the ’}’. In this example,
formatString.find(’}’)
gives us 12, exactly the right place for the end of the slice (one place past the actual end).
There is a subtle issue here that will be even more important later: We will kep wanting to find the next
brace,a dn not keep finding the first brace. How do we fix that?
Recall there was an alternate syntax for find, specifying the first place to search! That is what we need.
Where should we start? Well, the end must come after the start of the key, our variable start:
start = formatString.find(’{’) + 1
end = formatString.find(’}’, start)
Figuring out how to find the first key is important, but we are not home free yet. We need to come up with
code that works in a loop for the later keys. This code will not work for the next one. Why?
The search for ’{’ will again start from the beginning of the format string, and will find the first key
again. So what code will work for the second search? We search for the start of the next key going from the
end of the last one:
start = formatString.find(’{’, end) + 1
end = formatString.find(’}’, start)
This code will also work for later times through the loop: each time uses the end from the previous time
through the loop.
So now what do we do for finding the first key? We could separate the treatment of the first key from
all the others, but an easier approach would be to see if we can use the same code that already works for
the later repetitions, and initialize variables right to make it work. If we are to find the first key with
start = formatString.find(’{’, end) + 1
2.3. MAD LIBS REVISITED 63
then what do we need? Clearly end needs to have a value. (There will not be a previous loop to give it a
value.) What value should we initialize it to? The first search starts from the beginning of the string at
index 0, so the full code for this function is
def getKeys(formatString):
’’’formatString is a format string with embedded dictionary keys.
Return a list containing all the keys from the format string.’’’
keyList = list()
end = 0
repetitions = formatString.count(’{’)
for i in range(repetitions):
start = formatString.find(’{’, end) + 1
end = formatString.find(’}’, start)
key = formatString[start : end]
keyList.append(key)
return keyList
Look the code over and see that it makes sense. See how we continuously modify start, end, key, and
keyList. Since we have coded this new part as a function, it is easy to test without running a whole revised
mad lib program. We can just run this function on some test data, like the original story, and see what it
does. Run the example program testGetKeys.py:
def getKeys(formatString):
’’’formatString is a format string with embedded dictionary keys.
Return a list containing all the keys from the format string.’’’
keyList = list()
end = 0
repetitions = formatString.count(’{’)
for i in range(repetitions):
start = formatString.find(’{’, end) + 1
end = formatString.find(’}’, start)
key = formatString[start : end]
keyList.append(key)
return keyList
originalStory = """
Once upon a time, deep in an ancient jungle,
there lived a {animal}. This {animal}
liked to eat {food}, but the jungle had
very little {food} to offer. One day, an
explorer found the {animal} and discovered
it liked {food}. The explorer took the
{animal} back to {city}, where it could
eat as much {food} as it wanted. However,
the {animal} became homesick, so the
explorer brought it back to the jungle,
leaving a large supply of {food}.
The End
"""
print(getKeys(originalStory))
2.3. MAD LIBS REVISITED 64
"""
end = 0
repetitions = formatString.count(’{’)
for i in range(repetitions):
start = formatString.find(’{’, end) + 1
end = formatString.find(’}’, start)
key = formatString[start : end]
keyList.append(key) # may add duplicates
return set(keyList) # removes duplicates: no duplicates in a set
def getUserPicks(cues):
’’’Loop through the collection of cue keys and get user choices.
Return the resulting dictionary.
’’’
userPicks = dict()
for cue in cues:
addPick(cue, userPicks)
return userPicks
def tellStory(story):
’’’story is a format string with Python dictionary references embedded,
in the form {cue}. Prompt the user for the mad lib substitutions
and then print the resulting story with the substitutions.
’’’
cues = getKeys(story)
userPicks = getUserPicks(cues)
print(story.format(**userPicks))
def main():
originalStory = ’’’
Once upon a time, deep in an ancient jungle,
there lived a {animal}. This {animal}
liked to eat {food}, but the jungle had
very little {food} to offer. One day, an
explorer found the {animal} and discovered
it liked {food}. The explorer took the
{animal} back to {city}, where it could
eat as much {food} as it wanted. However,
the {animal} became homesick, so the
explorer brought it back to the jungle,
leaving a large supply of {food}.
The End
’’’
tellStory(originalStory)
main()
2.4. GRAPHICS 66
Does the use of well-named functions make it easier to follow this code? Make sure you follow the flow of
execution and data.
After Python file manipulation is introduced, in Exercise 2.5.0.3you can modify the program to work on
a madlib format string chosen by the user and taken from a file.
Exercise 2.3.3.1. ** Rename the example file locationsStub.py to be locations.py, and complete the
function printLocations, to print the index of each location in the string s where target is located. For exam-
ple, printLocations(’This is a dish’, ’is’) would go through the string ’This is a dish’ looking
for the index of places where ’is’ appears, and would return [2, 5, 11]. Similarly printLocations(’This
is a dish’, ’h’) would return [1, 13]. The program stub already uses the string method count. You
will need to add code using the more general form of find.
2.4. Graphics
Graphics make programming more fun for many people. To fully introduce graphics would involve many
ideas that would be a distraction now. This section introduces a simplified graphics module developed by
John Zelle for use with his Python Programming book. My slight elaboration of his package is graphics.py
in the example programs.
Repeated caution: In Windows XP or Vista, be sure to start Idle from the shortcut provided in the
examples (in the same directory as graphics.py). Do not start by clicking on an existing file to get a context
menu and choosing Open With Idle: The ’Open With Idle’ allows you to edit, but then when you go to run
your graphics program, it fails miserably and with no clear reason.
2.4.1. A Graphics Introduction in the Shell. Make sure you have Idle started from inside your
Python folder, and have graphics.py in the same folder, so the Python interpreter can find it.
Note: you will just be a user of the graphics.py code, so you do not need to understand the inner
workings! It uses all sorts of features of Python that are way beyond these tutorials. There is no particular
need to open graphics.py in the Idle editor.
You will definitely want to be a user of the graphical module.
In Idle, in the Shell, enter the following lines, one at a time and read the explanations:
from graphics import *
Zelle’s graphics are not a part of the standard Python distribution. For the Python interpreter to find Zelle’s
module, it must be imported. The line above makes all the types of object of Zelle’s module accessible, as if
they were already defined like built-in types str or list.
Pause after you enter the opening parenthesis below:
win = GraphWin(
The Idle editor tries to help you by displaying a pop-up tool tip with the parameter names and sometimes
a default value after an equal sign. The default value is used if you supply nothing. In this case we will use
the default values, so you can just finish by entering the closing parenthesis, now, completing
win = GraphWin()
Look around on your screen, and possibly underneath other windows: There should be a new window labeled
“Graphics Window”. Bring it to the top, and preferably drag it around to make it visible beside your Shell
window. A GraphWin is a type of object from Zelle’s graphics package that automatically displays a window
when it is created. The assignment statement remembers the window object as win for future reference.
(This will be our standard name for our graphics window object.) A small window, 200 by 200 pixels is
created. A pixel is the smallest little square that can by displayed on your screen. Modern screen usually
have more than 1000 pixels across the whole screen.
Again, pause after entering the opening parenthesis below, and see how Idle hints at the meaning of the
parameters to create a Point object. Then complete the line as given below:
pt = Point(100, 50)
This creates a Point object and assigns it the name pt. Unlike when a GraphWin is created, nothing is
immediately displayed: In theory you could have more than one GraphWin. Zelle designed the graphics
2.4. GRAPHICS 67
module so you must tell Python into which GraphWin to draw the Point. A Point object, like each of the
graphical objects that can be drawn on a GraphWin, has a method5draw. Enter
pt.draw(win)
Now you should see the Point if you look hard in the Graphics Window - it shows as a single, small, black
pixel. Graphics windows have a Cartesian (x,y) coordinate system. The dimensions are initially measured
in pixels. The first coordinate is the horizontal coordinate, measured from left to right, so 100 is about half
way across the 200 pixel wide window. The second coordinate, for the vertical direction, increases going
down from the top of the window by default, not up as you are likely to expect from geometry or algebra
class. The coordinate 50 out of the total 200 vertically should be about 1/4 of the way down from the top.
We will see later that we can reorient the coordinate system to fit our taste.
Enter both of the lines of code
cir = Circle(pt, 25)
cir.draw(win)
The first line creates a Circle object with center at the previously defined pt and with radius 25. This object
is remembered with the name cir. As with all graphics objects that may be drawn within a GraphWin, it is
only made visible by explicitly using its draw method:
So far, everything has been drawn in the default color black. Graphics objects like a Circle have methods
to change their colors. Basic color name strings are recognized. You can choose the color for the circle outline
as well as filling in the inside. Enter both lines
cir.setOutline("red")
cir.setFill("blue")
Now add
line = Line(pt, Point(150, 100))
line.draw(win)
A Line object is constructed with two Points as parameters. In this case we use the previously named Point,
pt, and specify another Point directly. Technically the Line object is a segment between the the two points.
A rectangle is also specified by two points. The points must be diagonally opposite corners. Try
rect = Rectangle(Point(20, 10), pt)
rect.draw(win)
You can move objects around in a GraphWin. This will be handy for animation, shortly. The parameters
to the move method are the amount to shift the x and y coordinates. See if you can guess the result before
you enter:
line.move(10, 40)
Did you remember that the y coordinate increases down the screen?
Feel free to further modify the graphics window using the methods introduced. To do this in a more
systematic and easily reproduced way, we will shortly switch to program files, but then you do not get to
see the effect of each statement individually and immediately!
Take your last look at the Graphics Window, and make sure that all the steps make sense. Then destroy
the window win with the GraphWin method close:
win.close()
An addition I have made to Zelle’s package is the ability to print a string value of graphics objects for
debugging purposes. Assuming you downloaded graphics.py from the hands-on site (not Zelle’s), continue
in the Shell with
print(line)
If some graphics object isn’t visible because it is underneath something else of off the screen, this sort of
output might be a good reality check.
5The basic ideas of objects and methods were introduced in Section 2.1.1.
2.4. GRAPHICS 68
2.4.2. Sample Graphics Programs. Here is a very simple program, face.py. The only interaction
is to click the mouse to close the graphics window. Have a directory window open to the Python examples
folder containing face.py. In Windows you can double click on the icon for face.py to run it.
After you have checked out the picture, click with the mouse inside the picture, as requested, to terminate
the program.
After you have run the program, you can examine the program in Idle or look below. The whole program
is shown first; smaller pieces of it are discussed later:
’’’A simple graphics example constructs a face from basic shapes.
’’’
def main():
winWidth = 200 # give a name to the window width
winHeight = 150 # and height
win = GraphWin(’Face’, winWidth, winHeight) # give title and dimensions
win.setCoords(0, 0, winWidth, winHeight) # make right side up coordinates!
main()
Let us look at individual parts.
Until further notice the set-off code is for you to read and have explained.
Immediately after the documentation string, always have the import line in your graphics program, to
allow easy access to the graphics.py module:
from graphics import *
Though not a graphics issue, the first two lines of the main method illustrate a very good practice:
winWidth = 200 # give a name to the window width
winHeight = 150 # and height
Important parameters for your programs should get names. Within the program the names will make more
sense to the human user than the literal data values. Plus, in this program, these parameters are used several
times. If I choose to change the window size to 400 by 350, I only need to change the value of each dimension
in one place!
2.4. GRAPHICS 69
While our earlier text-based Python programs have automatically terminated after the last line finishes
executing, that is not true for programs that create new windows: The graphics window must be explicitly
closed. The win.close() is necessary.
You can copy the form of this program for other simple programs that just draw a picture. The size and
title on the window will change, as well as the specific graphical objects, positions, and colors. Something
like the last four lines can be used to terminate the program.
Another simple drawing example is balloons.py. Feel free to run it and look at the code in Idle. Note
that the steps for the creation of all three balloons are identical, except for the location of the center of each
balloon, so a loop over a list of the centers makes sense.
The next example, triangle.py, illustrates similar starting and ending code. In addition it explicitly
interacts with the user. Rather than the code specifying literal coordinates for all graphical objects, the
program remembers the places where the user clicks the mouse, and uses them as the vertices of a triangle.
Return to the directory window for the Python examples. In Windows you can double click on the icon
for triangle.py to run it.
While running the program, follow the prompts in the graphics window and click with the mouse as
requested.
After you have run the program, you can examine the program in Idle or look below:
’’’Program: triangle.py or triangle.pyw (best name for Windows)
Interactive graphics program to draw a triangle,
with prompts in a Text object and feedback via mouse clicks.
Illustrates all of the most common GraphWin methods, plus
some of the ways to change the appearance of the graphics.
’’’
def main():
winWidth = 300
winHeight = 300
win = GraphWin(’Draw a Triangle’, winWidth, winHeight)
win.setCoords(0, 0, winWidth, winHeight) # make right-side-up coordinates!
win.setBackground(’yellow’)
p2 = win.getMouse()
p2.draw(win)
p3 = win.getMouse()
p3.draw(win)
win.getMouse()
win.close()
main()
Let us look at individual parts.
Until further notice the set-off code is for you to read and have explained.
The lines before
win.setBackground(’yellow’)
are standard starting lines (except for the specific values chosen for the width, height, and title). The
background color is a property of the whole graphics window that you can set. The line above illustrates
the last new common method of a GraphWin.
message = Text(Point(winWidth/2, 20), ’Click on three points’)
message.draw(win)
Again a Text object is created. We will see below how the Text object can be modified later. This is the
prompt for user action, expected for use in the lines
# Get and draw three vertices of triangle
p1 = win.getMouse()
p1.draw(win)
p2 = win.getMouse()
p2.draw(win)
p3 = win.getMouse()
p3.draw(win)
The win.getMouse() method (with no parameters), waits for you to click the mouse inside win. Then the
Point where the mouse was clicked is returned. In this code three mouse clicks are waited for, remembered
in variables p1, p2, and p3, and the points are drawn.
Next we introduce a very versatile type of graphical object, a Polygon, which may have any number of
vertices specified in a list as its parameter. We see that the methods setFill and setOutline that we used
earlier on a Circle, and the setWidth method available for a Line, also apply to a Polygon, (and also to
other graphics objects).
vertices = [p1, p2, p3]
triangle = Polygon(vertices)
triangle.setFill(’gray’)
triangle.setOutline(’cyan’)
triangle.setWidth(4)
triangle.draw(win)
The next few lines illustrate most of the ways a Text object may be modified. Not only may the text string be
changed. The appearance may be changed like in most word processors. The reference pages for graphics.py
give the details.
# Wait for a final click to exit
message.setText(’Click anywhere to quit.’)
message.setTextColor(’red’)
message.setStyle(’italic’)
message.setSize(20)
After this prompt (with its artificially elaborate styling), we have the standard finishing lines:
2.4. GRAPHICS 72
win.getMouse()
win.close()
2.4.3. A Windows Operating System Specialization: .pyw. This Windows-specific section is not
essential. It does describe how to make some Windows graphical programs run with less clutter.
If you ran the triangle.py program by double clicking its icon under Windows, you might have noticed a
console window first appearing, followed by the graphics window. For this program, there was no keyboard
input or screen output through the console window, so the console window was unused and unnecessary.
In such cases, under Windows, you can change the source file extension from .py to .pyw, suppressing the
display of the console window. If you are using windows, check it out.
The distinction is irrelevant inside Idle, which always has its Shell window.
2.4.4. Graphics.py vs. Event Driven Graphics. This optional section only looks forward to more
elaborate graphics systems than are used in this tutorial.
One limitation of the graphics.py module is that it is not robust if a graphics window is closed by clicking
on the standard operating system close button on the title bar. If you close a graphics window that way,
you are likely to get a Python error message. On the other hand, if your program creates a graphics window
and then terminates abnormally due to some other error, the graphics window may be left orphaned. In this
case the close button on the title bar is important: it is the easiest method to clean up and get rid of the
window!
This lack of robustness is tied to the simplification designed into the graphics module. Modern graphics
environments are event driven. The program can be interrupted by input from many sources including
mouse clicks and key presses. This style of programming has a considerable learning curve. In Zelle’s
graphics package, the complexities of the event driven model are pretty well hidden. If the programmer
wants user input, only one type can be specified at a time (either a mouse click in the graphics window via
the getMouse method, or via the input or input keyboard entry methods into the Shell window).
2.4.5. The Documentation for graphics.py. Thus far various parts of Zelle’s graphics package
have been introduced by example. A systematic reference to Zelle’s graphics package with the form of all
function calls is at http://mcsp.wartburg.edu/zelle/python/graphics/graphics/index.html. We have
introduced most of the important concepts and methods.
One special graphics input object type, Entry, will be discussed later. You might skip it for now.
Another section of the reference that will not be pursued in the tutorials is the Image class.
Meanwhile you can look at http://mcsp.wartburg.edu/zelle/python/graphics/graphics/index.
html. It is important to pay attention to the organization of the reference: Most graphics object share a
number of common methods. Those methods are described together, first. Then, under the headings for
specific types, only the specialized additional methods are discussed.
Exercise 2.4.5.1. * Make a program scene.py creating a scene with the graphics methods. You are
likely to need to adjust the positions of objects by trial and error until you get the positions you want. Make
sure you have graphics.py in the same directory as your program.
Exercise 2.4.5.2. * Elaborate your scene program so it becomes changeScene.py, and changes one or
more times when you click the mouse (and use win.getMouse()). You may use the position of the mouse
click to affect the result, or it may just indicate you are ready to go on to the next view.
2.4.6. Issues with Mutable Objects: The Case for clone. Zelle chose to have the constructor
for a Rectangle take diagonally opposite corner points as parameters. Suppose you prefer to specify only
one corner and also specify the width and height of the rectangle. You might come up with the following
function, makeRect, to return such a new Rectangle. Read the following attempt:
def makeRect(corner, width, height):
’’’Return a new Rectangle given one corner Point and the dimensions.’’’
corner2 = corner
corner2.move(width, height)
return Rectangle(corner, corner2)
2.4. GRAPHICS 73
The second corner must be created to use in the Rectangle constructor, and it is done above in two steps.
Start corner2 from the given corner and shift it by the dimensions of the Rectangle to the other corner.
With both corners specified, you can use Zelle’s version of the Rectangle constructor.
Unfortunately this is an incorrect argument. Run the example program makeRectBad.py:
’’’Program: makeRectBad.py
Attempt a function makeRect (incorrectly), which takes
a takes a corner Point and dimensions to construct a Rectangle.
’’’
def main():
winWidth = 300
winHeight = 300
win = GraphWin(’Draw a Rectangle (NOT!)’, winWidth, winHeight)
win.setCoords(0, 0, winWidth, winHeight)
rect = makeRect(Point(20, 50), 250, 200)
rect.draw(win)
main()
By stated design, this program should draw a rectangle with one corner at the point (20, 50) and the other
corner at (20+250, 50+200) or the point (270, 250), and so the rectangle should take up most of the 300
by 300 window. When you run it however that is not what you see. Look carefully. You should just see
one Point toward the upper right corner, where the second corner should be. Since a Rectangle was being
drawn, it looks like it is the tiniest of Rectangles, where the opposite corners are at the same point! Hm,
well the program did make the corners be the same initially. Recall we set
corner2 = corner
What happens after that?
Read and follow the details of what happens.
We need to take a much more careful look at what naming an object means. A good way to visualize
this association between a name and an object is to draw an arrow from the name to the object associated
with it. The object here is a Point, which has an x and y coordinate describing its state, so when the
makeRect method is started the parameter name corner is associated with the actual parameter, a Point
with coordinates (20, 50).
2.4. GRAPHICS 74
Next, the assignment statement associates the name corner2 with the same object. It is another name,
or alias, for the original Point.
corner2.move(width, height)
internally changes or mutates the Point object, and since in this case width is 250 and height is 200, the
coordinates of the Point associated with the name corner2 change to 20+250=270 and 50+200=250:
Look! The name corner is still associated with the same object, but that object has changed internally!
That is the problem: we wanted to keep the name corner associated with the point with original coordinates,
but it has been modified.
The solution is to use the clone method that is defined for all the graphical objects in graphics.py. It
creates a separate object, which is a copy with an equivalent state. We just need to change the line
corner2 = corner
to
corner2 = corner.clone()
Though corner and corner2 refer to points with equivalent coordinates, they do not refer to the same
object. Then after
corner2.move(width, height)
we get:
No conflict: corner and corner2 refer to the corners we want. Run the corrected example program,
makeRectange.py.
2.4.7. More on Mutable and Immutable Types. Read this section if you want a deeper under-
standing of the significance of muable and immutable objects.
This alias problem only came up because a Point is mutable. We had no such problems with the
immutable types int or str.
Read and follow the discussion of the following code.
Just for comparison, consider the corresponding diagrams for code with ints that looks superficially
similar:
a = 2
b = a
b = b + 3
The third line does not change the int object 2. The result of the addition operation refers to a different
object, 5, and the name b is assigned to it:
2.4.8. Animation. Run the example program, backAndForth0.py. The whole program is shown below
for convenience. Then each individual new part of the code is discussed individually:
’’’Test animation and depth.
’’’
from graphics import *
import time
def main():
winWidth = 300
6Actually, lists are even trickier, because the elements of a list are arbitrary: There can still be issues of dependence
between the original and cloned list if the elements of the list are themselves mutable, and then you choose to mutate an
element.
2.4. GRAPHICS 77
winHeight = 300
win = GraphWin(’Back and Forth’, winWidth, winHeight)
win.setCoords(0, 0, winWidth, winHeight)
main()
Read the discussion below of pieces of the code from the program above. Do not try to execute fragments
alone.
There is a new form of import statement:
from graphics import *
import time
The program uses a function from the time module. The syntax used for the time module is actually the
safer and more typical way to import a module. As you will see later in the program, the sleep function
used from the time module will be referenced as time.sleep(). This tells the Python interpreter to look
in the time module for the sleep function.
If we had used the import statement
from time import *
then the sleep function could just be referenced with sleep(). This is obviously easier, but it obscures
the fact that the sleep function is not a part of the current module. Also several modules that a program
imports might have functions with the same name. With the individual module name prefix, there is no
ambiguity. Hence the form import moduleName is actually safer than from moduleName import *.
You might think that all modules could avoid using any of the same function names with a bit of
planning. To get an idea of the magnitude of the issue, have a look at the number of modules available to
Python. Try the following in the in the Shell (and likely wait a number of seconds):
help(’modules’)
Without module names to separate things out, it would be very hard to totally avoid name collisions with
the enormous number of modules you see displayed, that are all available to Python!
Back to the current example program: The main program starts with standard window creation, and
then makes three objects:
2.4. GRAPHICS 78
This very simple loop animates cir1 moving in a straight line to the right. As in a movie, the illusion of
continuous motion is given by jumping only a short distance each time (increasing the horizontal coordinate
by 5). The time.sleep function, mentioned earlier, takes as parameter a time in seconds to have the program
sleep, or delay, before continuing with the iteration of the loop. This delay is important, because modern
computers are so fast, that the intermediate motion would be invisible without the delay. The delay can be
given as a decimal, to allow the time to be a fraction of a second.
The next three lines are almost identical to the previous lines, and move the circle to the left (-5 in the
horizontal coordinate each time).
for i in range(46): # animate cir1 to the left
cir1.move(-5, 0)
time.sleep(.05)
The window closing lines of this program include a slight shortcut from earlier versions.
Text(Point(winWidth/2, 20), ’Click anywhere to quit.’).draw(win)
The text object used to display the final message only needs to be referred to once, so a variable name is
not necessary: The result of the Text object returned by the constructor is immediately used to draw the
object. If the program needed to refer to this object again, this approach would not work.
The next example program, backAndForth1.py, it just a slight variation, looking to the user just like the
last version. Only the small changes are shown below. This version was written after noticing how similar
the two animation loops are, suggesting an improvement to the program: Animating any object to move in
a straight line is a logical abstraction well expressed via a function.
The loop in the initial version of the program contained a number of arbitrarily chosen constants, which
make sense to turn into parameters. Also, the object to be animated does not need to be cir1, it can be
any of the drawable objects in the graphics package. The name shape is used to make this a parameter:
def moveOnLine(shape, dx, dy, repetitions, delay):
for i in range(repetitions):
shape.move(dx, dy)
time.sleep(delay)
Then in the main function the two similar animation loops are reduced to a line for each direction:
moveOnLine(cir1, 5, 0, 46, .05)
moveOnLine(cir1, -5, 0, 46, .05)
2.4. GRAPHICS 79
Make sure you see these two lines with function calls behave the same way as the two animation loops in
the main program of the original version.
Run the next example version, backAndForth2.py. The changes are more substantial here, and the
display of the whole program is followed by display and discussion of the individual changes:
’’’Test animation of a group of objects making a face.
’’’
from graphics import *
import time
for i in range(repetitions):
moveAll(shapeList, dx, dy)
time.sleep(delay)
def main():
winWidth = 300
winHeight = 300
win = GraphWin(’Back and Forth’, winWidth, winHeight)
win.setCoords(0, 0, winWidth, winHeight) # make right side up coordinates!
main()
Read the following discussion of program parts.
Moving a single elementary shape is rather limiting. It is much more interesting to compose a more
complicated combination, like the face from the earlier example face.py. To animate such a combination,
you cannot use the old moveOnLine function, because we want all the parts to move together, not one eye
all the way across the screen and then have the other eye catch up! A variation on moveOnLine is needed
where all the parts move together. We need all the parts of the face to move one step, sleep, and all move
again, .... This could all be coded in a single method, but there are really two ideas here:
(1) Moving a group of objects one step.
(2) Animating a number of moves for the group.
This suggests two functions. Another issue is how to handle a group of elementary graphics objects. The
most basic combination of objects in Python is a list, so we assume a parameter shapeList, which is a
list of elementary graphics objects. For the first function, moveAll, just move all the objects in the list
one step. Since we assume a list of objects and we want to move each, this suggests a for-each loop:
def moveAll(shapeList, dx, dy):
’’’ Move all shapes in shapeList by (dx, dy).’’’
for shape in shapeList:
shape.move(dx, dy)
Having this function, we can easily write the second function moveAllOnLine, with a simple change from
the moveOnLine function, substituting the moveAll function for the line with the move method:
def moveAllOnLine(shapeList, dx, dy, repetitions, delay):
’’’Animate the shapes in shapeList along a line.
Move by (dx, dy) each time.
Repeat the specified number of repetitions.
Have the specified delay (in seconds) after each repeat.
’’’
for i in range(repetitions):
moveAll(shapeList, dx, dy)
time.sleep(delay)
The code in main to construct the face is the same as in the earlier example face.py. Once all the pieces
are constructed and colored, they must be placed in a list, for use in moveAllOnLine:
faceList = [head, eye1, eye2, mouth]
Then, later, the animation uses the faceList to make the face go back and forth:
moveAllOnLine(faceList, 5, 0, 46, .05)
moveAllOnLine(faceList, -5, 0, 46, .05)
This version of the program has encapsulated and generalized the moving and animating by creating functions
and adding parameters that can be substituted. Again, make sure you see how the functions communicate
to make the whole program work. This is an important and non-trivial use of functions.
2.4. GRAPHICS 81
for i in range(repetitions):
moveAll(shapeList, dx, dy)
time.sleep(delay)
eye2End1 = eye1Center.clone()
eye2End1.move(15, 0)
eye2End2 = eye2End1.clone()
eye2End2.move(10, 0)
eye2 = Line(eye2End1, eye2End2)
eye2.setWidth(3)
eye2.draw(win)
2.4. GRAPHICS 82
mouthCorner1 = center.clone()
mouthCorner1.move(-10, -10)
mouthCorner2 = mouthCorner1.clone()
mouthCorner2.move(20, -5)
mouth = Oval(mouthCorner1, mouthCorner2)
mouth.setFill("red")
mouth.draw(win)
def main():
winWidth = 300
winHeight = 300
win = GraphWin(’Back and Forth’, winWidth, winHeight)
win.setCoords(0, 0, winWidth, winHeight) # make right side up coordinates!
main()
Read the following discussion of program parts.
As mentioned above, the face construction function allows a parameter to specify where the center of
the face is. The other parameter is the GraphWin that will contain the face.
def makeFace(center, win):
then the head is easily drawn, using this center, rather than cir1 with specific center point (40, 100):
head = Circle(center, 25)
head.setFill("yellow")
head.draw(win)
For the remaining Points used in the construction there is the issue of keeping the right relation to the center.
This is accomplished much as in the creation of the second corner point in the makeRectange function in
Section 2.4.6. A clone of the original center Point is made, and then moved by the difference in the positions
of the originally specified Points. For instance, in the original face, the center of the head and first eye were
at (40, 110) and (30, 115). That means a shift between the two coordinates of (-10, 5), since 30-40 = -10
and 130-110 = 20.
2.4. GRAPHICS 83
mouthCorner1 = center.clone()
mouthCorner1.move(-10, -10)
mouthCorner2 = mouthCorner1.clone()
mouthCorner2.move(20, -5)
mouth = Oval(mouthCorner1, mouthCorner2)
mouth.setFill("red")
mouth.draw(win)
Finally, the list of elements for the face must be returned to the caller:
return [head, eye1, eye2, mouth]
Then in the main function, the program creates a face in exactly the same place as before, but using the
makeFace function, with the original center of the face Point(40, 100). Now with the makeFace function,
with its center parameter, it is also easy to replace the old cir2 with a whole face!
faceList = makeFace(Point(40, 100), win)
faceList2 = makeFace(Point(150,125), win)
The animation section is considerably elaborated in this version.
stepsAcross = 46
dx = 5
dy = 3
wait = .01
for i in range(3):
moveAllOnLine(faceList, dx, 0, stepsAcross, wait)
moveAllOnLine(faceList, -dx, dy, stepsAcross//2, wait)
moveAllOnLine(faceList, -dx, -dy, stepsAcross//2, wait)
The unidentified numeric literals that were used before are replaced by named values that easily identify the
meaning of each one. This also allows the numerical values to be stated only once, allowing easy modification.
The whole animation is repeated three times by the use of a simple repeat loop.
The animations in the loop body illustrate that the straight line of motion does not need to be horizontal.
The second and third lines use a non-zero value of both dx and dy for the steps, and move diagonally.
Make sure you see now how the whole program works together, including all the parameters for the
moves in the loop.
By the way, the documentation of the functions in a module you have just run in the Shell is directly
available. Try in the Shell:
help(moveAll)
Exercise 2.4.8.1. ** Save backAndForth3.py to the new name backAndForth4.py. Add a triangular
nose in the middle of the face in the makeFace function. Like the other features of the face, make sure the
2.4. GRAPHICS 84
position of the nose is relative to the center parameter. Make sure the nose is included in the final list of
elements of the face that get returned.
Exercise 2.4.8.2. ** Make a program faces.py that asks the user to click the mouse, and then draws
a face at the point where the user clicked. Elaborate this with a simple repeat loop, so a face appears for
each of 6 clicks.
Exercise 2.4.8.3. ** Animate two faces moving in different directions at the same time in a program
move2Faces.py. You cannot use the moveAllOnLine function. You will have to make a variation of your
own. You can use the moveAll function separately for each face. Hint: imagine the old way of making an
animated cartoon. If each face was on a separate piece of paper, and you wanted to animate them moving
together, you would place them separately, record one frame, move them each a bit toward each other, record
another frame, move each another bit toward each other, record another frame, .... In our animations “record
a frame” is replaced by a short sleep to make the position visible to the user. Make a loop to incorporate
the repetition of the moves.
2.4.9. Entry Objects. Read this section if you want to allow the user to enter text directly into a
graphics window.
When using a graphics window, the shell window is still available. Keyboard input can be done in the
normal text fashion, waiting for a response, and going on after the user presses the enter key. It is annoying
to make a user pay attention to two windows, so the graphics module provides a way to enter text inside a
graphics window, with the Entry type. The entry is a partial replacement for the input function.
Run the simple example, greet.py, which is copied below:
"""Simple example with Entry objects.
Enter your name, click the mouse, and see greetings.
"""
def main():
winWidth = 300
winHeight = 300
infoHeight = 15
win = GraphWin("Greeting", winWidth, winHeight)
win.setCoords(0,0, winWidth, winHeight)