0% found this document useful (0 votes)
22 views28 pages

Py Tutorial 57 84

The document discusses lists and strings in Python. It explains that lists are mutable and can be changed by using methods like append, which adds an element to the end of a list. Append is demonstrated being used in a for loop to accumulate elements into a new list. This pattern is shown in a program that takes a list of numbers and a multiplier, and returns a new list with each element of the original multiplied by the multiplier using append in a for loop.

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)
22 views28 pages

Py Tutorial 57 84

The document discusses lists and strings in Python. It explains that lists are mutable and can be changed by using methods like append, which adds an element to the end of a list. Append is demonstrated being used in a for loop to accumulate elements into a new list. This pattern is shown in a program that takes a list of numbers and a multiplier, and returns a new list with each element of the original multiplied by the multiplier using append in a for loop.

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.1.

STRINGS, PART III 57

>>> vals = [5, 7, 9, 22, 6, 8]


>>> vals[1]
7
>>> vals[-2]
6
>>> vals[1:4]
[7, 9, 22]
Unlike strings, lists are mutable, as you will see in Section 2.2.1. Indices and slices can also be used in
assignment statements to change lists, but in this tutorial we not need list indexing, and we will not discuss
this subject further.

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. More Classes and Methods


The classes and methods introduced here are all used for the revised mad lib program developed in the
next section.

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:

>>> print(multipleAll([3, 1, 7], 5))


[15, 5, 35]
’’’
# more to come
Clearly this will be repetitious. We will process each element of the list numList. A for-each loop with
numList is appropriate. Also we need to create more and more elements of the new list. The accumulation
pattern will work here, with a couple of wrinkles.

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:

>>> print(multipleAll([3, 1, 7], 5))


[15, 5, 35]
’’’

newList = list() #2
for num in numList: #3
newList.append(num*multiplier) #4
return newList #5
2.2. MORE CLASSES AND METHODS 60

print(multipleAll([3, 1, 7], 5)) #6


Make sure the result makes sense to you or follow the details of playing computer below.
Line numList multiplier newList num comment
1-5 - - - - definition
6 - - - - call function
1 [3, 1, 7 5 - set formal parameters
2 [3, 1, 7 5 []
3 [3, 1, 7 5 [] 3 first in list
4 [3, 1, 7 5 [15] 3 append 3*5 = 15
3 [3, 1, 7 5 [15] 1 next in list
4 [3, 1, 7 5 [15, 5] 1 append 1*5 = 5
3 [3, 1, 7 5 [15, 5] 7 last in list
4 [3, 1, 7 5 [15, 5, 35] 7 append 7*5 = 35
3 [3, 1, 7 5 [15, 5, 35] 7 done with list and loop
5 [3, 1, 7 5 [15, 5, 35] 7 return [15, 5, 35]
6 - - - - print [15, 3, 35]
Using a for-loop and append is a powerful and flexible way to derive a new list, but not the only way.4
2.2.2. Sets. A list may contain duplicates, as in [2, 1, 3, 2, 5, 5, 2]. This is sometimes useful,
and sometimes not. You may have learned in math class that a set is a collection that does not allow
repetitions (a set automatically removes repetitions suggested). Python has a type set. Like many type
names, it can be used to convert other types. In this case it makes sense to convert any collection, and the
process removes duplicates.Read and see what happens:
>>> numberList = [2, 1, 3, 2, 5, 5, 2]
>>> aSet = set(numberList)
>>> aSet
{1, 2, 3, 5}
Set literals are enclosed in braces. Like other collections, a set can be used as a sequence in a for-loop.
Read, and check it makes sense:
>>> for item in aSet:
print(item)

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.

2.3. Mad Libs Revisited


2.3.1. A Function to Ease the Creation of Mad Libs. The versions so far of the Mad Lib program
have been fairly easy to edit to contain a different mad lib:
(1) Come up with a new mad lib story as a format string
(2) Produce the list of cues to prompt the user with.
The first is a creative process. The second is a pretty mechanical process of looking at the story string and
copying out the embedded cues. The first is best left to humans. The second can be turned over to a Python
function to do automatically, as many times as we like, with any story – if we write it once.
Writing the Python code also takes a different sort of creativity! We shall illustrate a creative process.
This is a bigger problem than any we have taken on so far. It is hard to illustrate a creative process if the
overall problem is too simple.
Try and follow along. Read the sample code and pseudocode.
There is nothing to try in the Shell or editor until further notice.
If we follow the last version of the mad lib program, we had a loop iterating through the keys in the
story, and making a dictionary entry for each key. The main idea we follow here is to use the format string
to automatically generate the sequence of keys. Let us plan this unified task as a new function:
def getKeys(formatString):
’’’formatString is a format string with embedded dictionary keys.
Return a list containing all the keys from the format string.’’’
# more to come
The keys we want are embedded like {animal}. There may be any number of them in the format string. This
indeterminacy suggests a loop to extract them. At this point we have only considered for-loops. There is
no obvious useful sequence to iterate through in the loop (we are trying to create such a sequence). The only
pattern we have discussed that does not actively process each element of a significant list is a repeat-loop,
where we just use the loop to repeat the correct number of times. This will work in this case.
First: how many times do we want to pull out a key – once for each embedded format. So how do we
count those?
The count method is obviously a way to count. However we must count a fixed string, and the whole
embedded formats vary (with different keys in the middle. A common part is ’{’, and this should not
appear in the regular text of the story, so it will serve our purpose:
repetitions = formatString.count(’{’)
for i in range(repetitions):
...
This is certainly the most challenging code to date. Before jumping into writing it all precisely, we can give
an overall plan in pseudo-code. For a plan we need an idea of what quantities we are keeping track of, and
name them, and outline the sequence of operations with them.
Think about data to name:
In this case we are trying to find a list. We will need to extract one element at a time and add it to the
list, so we need a list, say keyList.
The central task is to identifying the individual keys. When we find a key we can call it key.
Think about identifying the text of individual keys. This may be too hard to think of in the abstract, so
let us use as a concrete example, and let us keep it simple for the moment. Suppose the data in formatString
starts off as follows. The lines with numbers are added to help us refer to the indices. Display of possible
data:
# 1111111111222222222233333333
# 01234567890123456789012345678901234567
’blah {animal} blah blah {food} ...’
2.3. MAD LIBS REVISITED 62

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

The functions should behave as advertised.


Look back on the process described to come up with the getKeys function. One way of approaching
the creative process of coding this function was provided. There are many other results and approaches
possible, but the discussion did illustrate a number of useful ideas which you might adapt to other problems,
in different orders and proportions, that are summarized in the next section.
2.3.2. Creative Problem Solving Steps.
• Clearly define the problem. Encapsulating the problem in a function is useful, with inputs as
parameters and results returned. Include a complete documentation string, and a clear example
(or examples) of what it is to do.
• If the problem is too complicated to just solve easily, straight away, it is often useful to construct
a representative concrete case and write down concrete steps appropriate to this problem.
• Think of the data in the problem, and give names to the pieces you will need to refer to. Clearly
identify the ideas that the names correspond to. When using sequences like lists or strings, you
generally need names not only for the whole collection, but also parts like items and characters or
substrings, and often indices that locate parts of the collection.
• Plan the overall approach to the problem using a mixture of Python and suggestive phrases (called
pseudo-code). The idea is to refine it to a place where you can fairly easily figure how to replace
the phases with Python.
• Replace your pseudo-code parts with Python. If you had a concrete example to guide, you may
want one of more further concrete examples with different specific data, to make sure you come up
with code for a generalization that works in all cases.
• Recognize where something is being repeated over and over, and think how to structure appropriate
loops. Can you incorporate any patterns you have seen before?
• If you need to create a successive modification loop, think of how to approach the first repetition
and then how to modify the data for the later times through the loop. Usually you can make
the first time through the loop fit the more general pattern needed for the repetitions by making
appropriate initializations before the loop.
• Check and test your code, and correct as necessary.
2.3.3. The Revised Mad Lib Program. There is still an issue for use of getKeys in the mad lib
program: the returned list has repetitions in it, that we do not want in the mad lib program. We can easily
create a collection without repetitions, how?
One approach is to make a set from the list returned. A neater approach would be to just have the
getKeys function return a set in the first place. We need to slightly change to getKeys’ documentation
string and the final return line. This will be included in a new version of the mad lib program, which makes
it easy to substitute a new story. We will make the story’s format string be a parameter to the central
method, tellStory. We will also put the clearly identified step of filling the dictionary with the user’s
picks in a separate function. We will test tellStory with the original story. Note the changes included in
madlib2.py and run:
"""
madlib2.py
Interactive display of a mad lib, which is provided as a Python format string,
with all the cues being dictionary formats, in the form {cue}.
In this version, the cues are extracted from the story automatically,
and the user is prompted for the replacements.

Original mad lib adapted from code of Kirby Urner

"""

def getKeys(formatString): # change: returns a set


’’’formatString is a format string with embedded dictionary keys.
Return a set containing all the keys from the format string.’’’
keyList = list()
2.3. MAD LIBS REVISITED 65

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 addPick(cue, dictionary): # from madlib.py


’’’Prompt the user and add one cue to the dictionary.’’’
prompt = ’Enter an example for ’ + cue + ’: ’
dictionary[cue] = input(prompt)

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.
’’’

from graphics import *

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!

head = Circle(Point(40,100), 25) # set center and radius


head.setFill("yellow")
head.draw(win)

eye1 = Circle(Point(30, 105), 5)


eye1.setFill(’blue’)
eye1.draw(win)

eye2 = Line(Point(45, 105), Point(55, 105)) # set endpoints


eye2.setWidth(3)
eye2.draw(win)

mouth = Oval(Point(30, 90), Point(50, 85)) # set corners of bounding box


mouth.setFill("red")
mouth.draw(win)

message = Text(Point(winWidth/2, 20), ’Click anywhere to quit.’)


message.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.
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

win = GraphWin(’Face’, winWidth, winHeight) # give title and dimensions


win.setCoords(0, 0, winWidth, winHeight) # make right side up coordinates!
The first line shows the more general parameters for constructing a new GraphWin, a window title and
dimensions in pixels. The second line shows how to turn the coordinate system right-side-up, so the y
coordinate increases up the screen. The setCoords method sets up a new coordinate system, where the first
two numbers are the coordinates you wish to use for the lower left corner of the window, and the last two
numbers are the coordinates of the upper right corner. Thereafter, all coordinates are given in the new
coordinate system, and the graphics module silently calculates the correct underlying pixel positions. All
the lines of code up to this point in the program are my standard graphics program starting lines (other
than the specific values for the title and dimensions). You will likely start your programs with similar code.
head = Circle(Point(40,100), 25) # set center and radius
head.setFill("yellow")
head.draw(win)

eye1 = Circle(Point(30, 105), 5)


eye1.setFill(’blue’)
eye1.draw(win)
The lines above create two circles, in each case specifying the centers directly. They are filled in and made
visible.
eye2 = Line(Point(45, 105), Point(55, 105)) # set endpoints
eye2.setWidth(3)
eye2.draw(win)
The code above draws and displays a line, and illustrates another method available to graphics object,
setWidth, making a thicker line.

mouth = Oval(Point(30, 90), Point(50, 85)) # set corners of bounding box


mouth.setFill("red")
mouth.draw(win)
The code above illustrates another kind of graphics object, an Oval (or ellipse). There are several ways an
oval could be specified. Zelle chose to have you specify the corners of the bounding box that is just as high
and as wide as the oval. This rectangle is only imagined, not actually drawn. (If you want to see such a
rectangle, create a Rectangle object with the same two Points as paramaeters..)
The exact coordinates for the parts were determined by a number of trial-and-error refinements to the
program. An advantage of graphics is that you can see the results of your programming, and make changes
if you do not like the results!
The final action is to have the user signal to close the window. Just as with waiting for keyboard input
from input or input, it is important to prompt the user before waiting for a response! In a GraphWin, the
prompt must be made with a Text object displayed explicitly before the response is expected. Lines like the
following will often end a program that has a final displayed picture:
message = Text(Point(winWidth/2, 20), ’Click anywhere to quit.’)
message.draw(win)
win.getMouse()
win.close()
The parameters to construct the Text object are the point at the center of the text, and the text string
itself. See how the text position is set up to be centered from left to right, half way across the window’s
width. Also note, that because the earlier win.setCoord call put the coordinates in the normal orientation,
the y coordinate, 20, is close to the bottom of the window.
After the first two lines draw the promping text, the line win.getMouse() waits for a mouse click. In
this program, the position is not important. (In the next example the position of this mouse click will be
used.) As you have seen before, win.close() closes the graphics window.
2.4. GRAPHICS 70

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.
’’’

from graphics import *

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’)

message = Text(Point(winWidth/2, 20), ’Click on three points’)


message.draw(win)

# 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)

vertices = [p1, p2, p3]


triangle = Polygon(vertices)
triangle.setFill(’gray’)
triangle.setOutline(’cyan’)
triangle.setWidth(4) # width of boundary line
triangle.draw(win)

# Wait for a final click to exit


2.4. GRAPHICS 71

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


message.setTextColor(’red’)
message.setStyle(’italic’)
message.setSize(20)

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.
’’’

from graphics import *

def makeRect(corner, width, height): # Incorrect!


’’’Return a new Rectangle given one corner Point and the dimensions.’’’
corner2 = corner
corner2.move(width, height)
return Rectangle(corner, corner2)

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)

# Wait for another click to exit


msg = Text(Point(winWidth/2, 20),’Click anywhere to quit.’)
msg.draw(win)
win.getMouse()
win.close()

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.

The next line,

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()

A diagram of the situation after the cloning is:


2.4. GRAPHICS 75

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

After the first two lines we have an alias again:


2.4. GRAPHICS 76

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:

Hence a is still associated with the integer 2 – no conflict.


It is not technically correct to think of b as being the number 2, and then 5, but a little sloppiness of
thought does not get you in trouble with immutable types. With mutable types, however, be very careful of
aliases. Then it is very important to remember the indirectness: that a name is not the same thing as the
object it refers to.
Another mutable type is list. A list can be cloned with the slice notation: [:]. Try the following in
the Shell:6
nums1 = [1, 2, 3]
nums2 = nums1[:]
nums2.append(4)
nums1
nums2

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)

rect = Rectangle(Point(200, 90), Point(220, 100))


rect.setFill("blue")
rect.draw(win)

cir1 = Circle(Point(40,100), 25)


cir1.setFill("yellow")
cir1.draw(win)

cir2 = Circle(Point(150,125), 25)


cir2.setFill("red")
cir2.draw(win)

for i in range(46): # animate cir1 to the right


cir1.move(5, 0)
time.sleep(.05)
for i in range(46): # animate cir1 to the left
cir1.move(-5, 0)
time.sleep(.05)

# Wait for a final click to exit


Text(Point(winWidth/2, 20), ’Click anywhere to quit.’).draw(win)
win.getMouse()
win.close()

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

rect = Rectangle(Point(200, 90), Point(220, 100))


rect.setFill("blue")
rect.draw(win)

cir1 = Circle(Point(40,100), 25)


cir1.setFill("yellow")
cir1.draw(win)

cir2 = Circle(Point(150,125), 25)


cir2.setFill("red")
cir2.draw(win)
Zelle’s reference pages do not mention the fact that the order in which these object are first drawn is
significant. If objects overlap, the ones which used the draw method later appear on top. Other object
methods like setFill or move do not alter which are in front of which. This becomes significant when cir1
moves. The moving cir1 goes over the rectangle and behind cir2. (Run the program again if you missed
that.)
The animation starts with the code for a simple repeat loop:
for i in range(46): # animate cir1 to the right
cir1.move(5, 0)
time.sleep(.05)

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

def moveAll(shapeList, dx, dy):


’’’ Move all shapes in shapeList by (dx, dy).’’’
for shape in shapeList:
shape.move(dx, dy)

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)

def main():
winWidth = 300
winHeight = 300
win = GraphWin(’Back and Forth’, winWidth, winHeight)
win.setCoords(0, 0, winWidth, winHeight) # make right side up coordinates!

rect = Rectangle(Point(200, 90), Point(220, 100))


rect.setFill("blue")
rect.draw(win)

head = Circle(Point(40,100), 25)


head.setFill("yellow")
head.draw(win)

eye1 = Circle(Point(30, 105), 5)


eye1.setFill(’blue’)
eye1.draw(win)

eye2 = Line(Point(45, 105), Point(55, 105))


eye2.setWidth(3)
eye2.draw(win)

mouth = Oval(Point(30, 90), Point(50, 85))


mouth.setFill("red")
mouth.draw(win)

faceList = [head, eye1, eye2, mouth]


2.4. GRAPHICS 80

cir2 = Circle(Point(150,125), 25)


cir2.setFill("red")
cir2.draw(win)

moveAllOnLine(faceList, 5, 0, 46, .05)


moveAllOnLine(faceList, -5, 0, 46, .05)

Text(Point(winWidth/2, 20), ’Click anywhere to quit.’).draw(win)


win.getMouse()
win.close()

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

Run the example program backAndForth3.py.


The final version, backAndForth3.py, uses the observation that the code to make a face embodies one
unified idea, suggesting encapsulation inside a function. Once you have encapsulated the code to make a
face, we can make several faces! Then the problem with the original code for the face is that all the positions
for the facial elements are hard-coded: The face can only be drawn in one position. The full listing of
backAndForth3.py below includes a makeFace function with a parameter for the position of the center of
the face.
Beneath the listing of the whole program is a discussion of the individual changes:
’’’Test animation of a group of objects making a face.
Combine the face elements in a function, and use it twice.
Have an extra level of repetition in the animation.
’’’

from graphics import *


import time

def moveAll(shapeList, dx, dy):


’’’ Move all shapes in shapeList by (dx, dy).’’’
for shape in shapeList:
shape.move(dx, dy)

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)

def makeFace(center, win): #NEW


’’’display face centered at center in window win.
Return a list of the shapes in the face.
’’’

head = Circle(center, 25)


head.setFill("yellow")
head.draw(win)

eye1Center = center.clone() # face positions are relative to the center


eye1Center.move(-10, 5) # locate further points in relation to others
eye1 = Circle(eye1Center, 5)
eye1.setFill(’blue’)
eye1.draw(win)

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)

return [head, eye1, eye2, mouth]

def main():
winWidth = 300
winHeight = 300
win = GraphWin(’Back and Forth’, winWidth, winHeight)
win.setCoords(0, 0, winWidth, winHeight) # make right side up coordinates!

rect = Rectangle(Point(200, 90), Point(220, 100))


rect.setFill("blue")
rect.draw(win)

faceList = makeFace(Point(40, 100), win) #NEW


faceList2 = makeFace(Point(150,125), win) #NEW

stepsAcross = 46 #NEW section


dx = 5
dy = 3
wait = .05
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)

Text(Point(winWidth/2, 20), ’Click anywhere to quit.’).draw(win)


win.getMouse()
win.close()

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

eye1Center = center.clone() # face positions are relative to the center


eye1Center.move(-10, 5) # locate further points in relation to others
eye1 = Circle(eye1Center, 5)
eye1.setFill(’blue’)
eye1.draw(win)
The only other changes to the face are similar, cloning and moving Points, rather than specifying them with
explicit coordinates.
eye2End1 = eye1Center.clone()
eye2End1.move(15, 0)
eye2End2 = eye2End1.clone()
eye2End2.move(10, 0)
eye2 = Line(eye2End1, eye2End2)
eye2.setWidth(3)
eye2.draw(win)

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.
"""

from graphics import *

def main():
winWidth = 300
winHeight = 300
infoHeight = 15
win = GraphWin("Greeting", winWidth, winHeight)
win.setCoords(0,0, winWidth, winHeight)

instructions = Text(Point(winWidth/2, 40),


"Enter your name.\nThen click the mouse.")
instructions.draw(win)

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()

greeting1 = ’Hello, ’ + name + ’!’


Text(Point(winWidth/3, 150), greeting1).draw(win)

greeting2 = ’Bonjour, ’ + name + ’!’


Text(Point(2*winWidth/3, 100), greeting2).draw(win)

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


win.getMouse()
win.close()

You might also like