CSC111 Lab 9 2015
--D. Thiebaut (talk) 07:14, 29 March 2015 (EDT)
Exceptions
Part 1: Preparation
- Create a new program called lab9_1.py, and copy this code to the new Idle window.
# lab9_1.py # Your name here # getInput: returns an integer larger # than 0. Expected to be robust def getInput(): while True: x = int( input( "Enter an integer greater than 0: " ) ) if x <= 0: print( "Invalid entry. Try again!" ) else: return x def main(): num = getInput() print( "You have entered", num ) main()
- Test it with numbers such as -3, -10, 0, 5. Verify that the input function works well when you enter numbers.
- Test your program again, and this time enter expressions such as "6.3", or "hello" (without the quotes).
- Make a note of the Error reported by Python:
- Modify your function and add the highlighted code below by hand. This code will catch the ValueError exception.
# getInput: returns an integer larger # than 0. Catches Value errors def getInput(): # repeat forever... while True: # try to get an int try: x = int( input( "Enter an integer greater than 0: " ) ) except ValueError: # the user must have entered something other than an int print( "Invalid entry. Not an integer. Try again!" ) continue # No errors caught. See if the number is negative if x <= 0: print( "You entered a negative number. Try again!" ) else: # finally, we can return x as it is an int that is >0 return x
- Run your program and try different invalid inputs, such as strings or floats. You can also try just pressing the Return key, indicating that you are not providing anything to the input function. Verify that your program catches all these invalid entries and does not crash.
Review Class Example
- Below is an example we saw in class, and that is taken from Zelle. It illustrates how we can guard some code against several types of errors. Review it. You will need to follow this example for the next set of exercises.
def ZelleExample(): import math print( "solution to quadratic equation" ) try: a, b, c = eval( input( "enter 3 coefficients (a,b,c) " ) ) disc = math.sqrt( b*b - 4*a*c ) root1 = (-b + disc )/ (2*a) root2 = (+b + disc )/ (2*a) print( "solutions: ", root1, root2 ) except NameError: print( "You didn't enter 3 numbers" ) except TypeError: print( "Your inputs were not all numbers" ) except SyntaxError: print( "Forgot commas between the numbers?" ) except ValueError: print( "No real roots, negative discriminant" ) except: print( "Something went wrong..." )
Part 2: Exercise
- Create a new program called lab9_2.py with the code below:
def example1(): for i in range( 3 ): x = int( input( "enter an integer: " ) ) y = int( input( "enter another integer: " ) ) print( x, '/', y, '=', x/y ) def example2( L ): print( "\n\nExample 2" ) sum = 0 for i in range( len( L ) ): sum += L[i] print( "sum of items in ", L, "=", sum ) def printUpperFile( fileName ): file = open( fileName, "r" ) for line in file: print( line.upper() ) file.close() def createTextFile( fileName ): file = open( fileName, "w" ) file.write( "Welcome\nto\nCSC111\nIntroduction\nto\nComp.\nSci.\n" ) file.close() def main(): # create a text file for use later... createTextFile( "csc111.txt" ) # test first function example1() # test second function L = [ 10, 3, 5, 6, 9, 3 ] example2( L ) #example2( [ 10, 3, 5, 6, "NA", 3 ] ) # test third function fileName = input( "Enter name of file to display (type csc111.txt): " ) printUpperFile( fileName ) main()
- The program above has many flaws; it is not very robust. We can easily make it crash.
- Observe each function. Run your program a few times.
- Figure out how to make each function crash
- Go ahead and start forcing the first function to crash. Register the XXXXError that is generated. For example, if the output of the crash looks like this:
Traceback (most recent call last):
File "/Users/thiebaut/Desktop/except0.py", line 29, in <module>
main()
File "/Users/thiebaut/Desktop/except0.py", line 27, in main
example3( [ 10, 3, 5, 6 ] )
File "/Users/thiebaut/Desktop/except0.py", line 18, in example3
sum = sum + L[i]
IndexError: list index out of range
- what you are interested in is IndexError. This is the exception you want to guard your code against.
try: ........ ........ except IndexError: .........
- Add the try/except statement inside the function, and verify that your function is now more robust and does not crash on the same input that made it crash before.
- Repeat the same process for the other functions.
Classes and Objects
Cats
Below is the program we saw in class, where we create a Cat class, where each cat is defined by a name, a breed, whether it is vaccinated or not, and and age.
# cats1.py # D. Thiebaut # Program for Week #9 # Define a Cat class, and # use it to create a collection of # cats. class Cat: """a class that implements a cat and its information. Name, breed, vaccinated, tattooed, and age.""" def __init__( self, na, brd, vacc, ag ): """constructor. Builds a cat with all its information""" self.name = na self.breed = brd self.vaccinated = vacc self.age = ag def isVaccinated( self ): """returns True if cat is vaccinated, False otherwise""" return self.vaccinated def getAge( self ): """returns cat's age""" return self.age def getName( self ): """return name of cat""" return self.name def __str__( self ): """default string representation of cat""" vacc = "vaccinated" if not self.vaccinated: vacc = "not vaccinated" return "{0:20}:==> {1:1}, {2:1}, {3:1} yrs old".format( self.name, self.breed, vacc, self.age ) def main(): """ main program. Creates a list of cats and displays groups sharing the same property. Minou, 3, vac, stray Max, 1, not-vac, Burmese Gizmo, 2, vac, Bengal Garfield, 4, not-vac, Orange Tabby """ cats = [] cat = Cat( "Minou", "stray", True, 3 ) cats.append( cat ) cats.append( Cat( "Max", "Burmese", False, 1 ) ) cats.append( Cat( "Gizmo", "Bengal", True, 2 ) ) cats.append( Cat( "Garfield", "Orange Tabby", False, 4 ) ) # print the list of all the cats print( "\nComplete List:" ) for cat in cats: print( cat ) # print only the vaccinated cats print( "\nVaccinated cats:" ) for cat in cats: if cat.isVaccinated()==True: print( cat ) # print cats older than 2 print( "\nCats 2 or older:" ) for cat in cats: if cat.getAge()>=2: print( cat ) if __name__=="__main__": main()
- Recreate this code in a program called Lab9_2.py
- Run it.
- Modify it by adding a new method to your Cat class, so that the main program outputs all the stray cats.
- Modify it again, and make the main program output the non-vaccinated cats 2 or older.
Challenge #1: Tattooed Cats |
- Assume that we now want to keep track of whether cats are tattooed or not. A tattoo is a good way to mark cats so that they can be easily identified if lost.
- Add a new boolean field to the class for the tattooed status. True will mean tattooed, False, not tattooed.
- Modify the constructor so that we can pass the tattooed status along with all the other information
- Add a method to Cat, called isTattooed(), that will allow one to test if a cat is tattooed or not.
- Modify your main program so that the tattoo status is included when each cat object is created. You may want to use this new list of cats:
Minou, 3, vaccinated, tattooed, stray Max, 1, not vaccinated, tattooed, Burmese Gizmo, 2, vaccinated, tattooed, Bengal Garfield, 4, not vaccinated, not tattooed, Orange Tabby
- Modify the __str__() method so that it includes the tattoo information in the returned string.
- Run your main program again. It should output the same information as before, but this time the tattoo information will also appear when a cat is listed.
- Add a new section to your main program that will output all the cats that are not tattooed.
Challenge #2: Vaccinated and Tattooed Cats |
- Make the main program display all the cats that are vaccinated and tattooed.
Challenge #3: Reading the Cat information from a CSV file |
- Play with the program below:
# Lab9CatsCSV.py # D. Thiebaut # Seed for a program that reads cat data from # a CSV file. def createCatCSV( fileName ): file = open( fileName, "w" ) file.write( """Minou, 3, vaccinated, tattooed, stray Max, 1, not vaccinated, tattooed, Burmese Gizmo, 2, vaccinated, tattooed, Bengal Garfield, 4, not vaccinated, not tattooed, Orange Tabby Silky, 3, vaccinated, tattooed, Siemese Winston, 1, not vaccinated, not tattooed, stray\n""" ) file.close() def main(): fileName = "cats.csv" createCatCSV( fileName ) # open a csv file file = open( fileName, "r" ) # process each line of the file for line in file: # split the line at the commas words = line.strip().split( "," ) # skip lines that do not have 5 fields if len( words ) != 5: continue # print the fields for i in range( len( words ) ): print( "words[",i,"]=", words[i].strip(), end=", " ) print() if __name__=="__main__": main()
- Merge this program and the one you wrote for Challenge #2, and make your program read the cats from a CSV file, extract each cat from a line of the file, and store it into a Cat object, and keep the collection of cat in a list. Then make the main program output the same lists of cats as before.
Wheel
You may find the following document describing the different graphic objects introduce in the Zelle's textbook useful.
Classes for a wheel and a Car
Below is the code we wrote in class this week:
# carNew.py
# D. Thiebaut
# first program using a class to define
# a new graphic object: a wheel, which
# is made of 2 concentric circles. The class
# Wheel supports methods to initialize the
# graphics object, draw it on the window,
# and move it.
from graphics import *
W = 400 # width of the graphics window
H = 400 # height of the graphics window
#----------------------------------------------------------------
class Wheel:
"""A class with two concentric circles"""
def __init__( self, center, r1, r2 ):
"""constructor"""
self.radius1 = min( r1, r2 )
self.radius2 = max( r1, r2 )
self.circ1 = Circle( center, self.radius1 )
self.circ2 = Circle( center, self.radius2 )
def draw( self, win ):
"""draws the wheel on the graphics window win"""
self.circ2.draw( win )
self.circ1.draw( win )
def move( self, dx, dy ):
self.circ1.move( dx, dy )
self.circ2.move( dx, dy )
def setFill( self, color1, color2 ):
self.circ1.setFill( color1 )
self.circ2.setFill( color2 )
def getRadius1( self ):
"""returns the smallest radius"""
return self.radius1
def getRadius2( self ):
"""returns the largest radius"""
return self.radius2
class Car:
def __init__( self, P1, P2 ):
self.body = Rectangle( P1, P2 )
w =abs( P2.getX()-P1.getX() )
h =abs( P2.getY()-P1.getY() )
center1 = Point( w/4+P1.getX(), P2.getY() )
center2 = Point( P2.getX()-w/4, P2.getY() )
r2 = w/8
r1 = r2/2
self.w1 = Wheel( center1, r1, r2 )
self.w2 = Wheel( center2, r1, r2 )
def setFill( self, bodyc, tirec, insidec ):
self.body.setFill( bodyc )
self.w1.setFill( tirec, insidec )
self.w2.setFill( tirec, insidec )
def draw( self, win ):
self.body.draw( win )
self.w1.draw( win )
self.w2.draw( win )
def move( self, dx, dy):
self.body.move( dx, dy )
self.w1.move( dx, dy )
self.w2.move( dx, dy )
#----------------------------------------------------------------
def waitForClick( win, message ):
""" waitForClick: stops the GUI and displays a message.
Returns when the user clicks the window. The message is erased."""
# wait for user to click mouse to start
startMsg = Text( Point( win.getWidth()/2, win.getHeight()/2 ), message )
startMsg.draw( win ) # display message
win.getMouse() # wait
startMsg.undraw() # erase
def main():
"""demo program for wheel: draws wheel on screen"""
global W, H
win = GraphWin( "wheel demo", W, H )
waitForClick( win, "click to start" )
#--- create new wheel object ---
#w = Wheel( Point( W/2, H/2 ), 20, 40 )
#w.setFill( "black", "yellow" )
#w.draw( win )
#--- create a car object ---
car = Car( Point( 20, 20 ), Point( 120, 60 ) )
car.setFill( "red", "yellow", "black" )
car.draw( win )
waitForClick( win, "click to move" )
#--- move the car ---
for i in range( 100 ):
car.move( 3, 0 )
waitForClick( win, "click to end" )
main()
- Run the program a few times to make sure you understand how it works.
- Assume that the programmer writing the main program (we always assume that the programmer writing the main program is different from the programmer writing the classes, as is most often the case in real life) forgot the rule that when building a car, the first point passed to the constructor is the top left point of the rectangle defining the body, and that the second point is the bottom right point of the body.
- Instead the programmer creates the object car as follows:
car = Car( Point( 120, 60 ), Point( 20, 20 ) )
- Make this modification and run your program. What happens?
- Fix the class Car so that the user can specify any point in any order (top-left, bottom-right), or (top-right, bottom-left), or (bottom-left, top-right), and the constructor will correctly define the body and the wheels to fit the shape defined by the two points.
- Oh, by the way, the car is a two-door convertible. Add a door to the car. The door should be a simple rectangle in the middle of the body of the car, between the two wheels. (You may want to sketch the shape of the car on paper and figure out what proportions to use to position the door.)
- Add a windshield to the car as well. You are free to define its shape, and the Polygon class will provide a nice solution for you. Polygons are defined in Section 3.6 of Zelle's 4-page document on his graphics library.
Random cars going in random directions
- Make your program create a list of 5 cars.
- Each car has a random horizontal velocity (dx != 0, dy=0), making it go left, or right. Make dx and dy part of the class, i.e. make these variables member variables of the class Car.
- Make your program move all 5 cars at the same time, until all the cars have exited the window. As soon as the cars all disappear your program will stop. Think of an efficient way to do this... This is a bit tricky... (But we are at a time in the semester where you can deal with tricky questions... :-)
<showafterdate after="20150403 11:00" before="20150601 00:00">
Solution Programs
Part 1
# lab9 programs
# D. Thiebaut
# getInput: returns an integer larger
# than 0. Expected to be robust
def getInput():
while True:
x = int( input( "Enter an integer greater than 0: " ) )
if x <= 0:
print( "Invalid entry. Try again!" )
else:
return x
# betterGetInput: returns an integer larger
# than 0. Expected to be robust
def betterGetInput():
# repeat forever...
while True:
# try to get an int
try:
x = int( input( "Enter an integer greater than 0: " ) )
except ValueError:
# the user must have entered something other than an int
print( "Invalid entry. Not an integer. Try again!" )
continue
# There was no errors. See if the number is negative
if x <= 0:
print( "You entered a negative number. Try again!" )
else:
return x
def main1():
num = betterGetInput()
print( "You have entered", num )
# =======================================================================
def example1():
print( "You will need to enter 3 pairs of ints..." )
while True:
try:
x = int( input( "enter a number: " ) )
y = int( input( "enter another number: " ) )
print( x, '/', y, '=', x/y )
break
except ZeroDivisionError:
print( "Can't divide by 0!" )
except ValueError:
print( "That doesn't look like a number!" )
except:
print( "something unexpected happend!" )
def example2( L ):
print( "\n\nExample 2" )
sum = 0
for i in range( len( L ) ):
try:
sum += L[i]
except TypeError:
continue
print( "sum of items in ", L, "=", sum )
def printUpperFile( fileName ):
try:
file = open( fileName, "r" )
except FileNotFoundError:
print( "***Error*** File", fileName, "not found!" )
return
# if we're here, the file is found and open
for line in file:
print( line.upper() )
file.close()
def createTextFile( fileName ):
file = open( fileName, "w" )
file.write( "Welcome\nto\nCSC111\nIntroduction\nto\nComp.\nSci.\n" )
file.close()
def main2():
createTextFile( "csc111.txt" )
example1()
L = [ 10, 3, 5, 6, 9, 3 ]
example2( L )
#example2( [ 10, 3, 5, 6, "NA", 3 ] )
printUpperFile( "csc111.txt" )
#printUpperFile( "csc1111.txt" )
main2()
Part 2
# Lab9Part2.py
# D. Thiebaut
# A solution program for the first part of Lab 10. The goal
# is to create a list of car objects constructed from the Car
# class. The Car class is made of a rectangular body, two
# wheels, a door, and a windshield.
# The cars are given a random direction (which cannot be
# 0) and move in that direction until all the cars have escaped
# the graphics window.
# This program uses Zelle's graphics library.
from graphics import *
import random
W = 400 # width of the graphics window
H = 400 # height of the graphics window
#----------------------------------------------------------------
class Wheel:
"""A class with two concentric circles"""
def __init__( self, center, r1, r2 ):
"""constructor"""
self.radius1 = min( r1, r2 )
self.radius2 = max( r1, r2 )
self.circ1 = Circle( center, self.radius1 )
self.circ2 = Circle( center, self.radius2 )
def draw( self, win ):
"""draws the wheel on the graphics window win"""
self.circ2.draw( win )
self.circ1.draw( win )
def move( self, dx, dy ):
self.circ1.move( dx, dy )
self.circ2.move( dx, dy )
def setFill( self, color1, color2 ):
self.circ1.setFill( color1 )
self.circ2.setFill( color2 )
def getRadius1( self ):
"""returns the smallest radius"""
return self.radius1
def getRadius2( self ):
"""returns the largest radius"""
return self.radius2
#----------------------------------------------------------------
class Car:
def __init__( self, P1, P2, dx ):
"""constructor. Receives top-left and bottom-right point, and speed"""
#--- create additional points needed for the car ---
pp1 = Point( min( P1.getX(), P2.getX() ), min( P1.getY(), P2.getY() ) )
pp2 = Point( max( P1.getX(), P2.getX() ), max( P1.getY(), P2.getY() ) )
pp3 = Point( (pp1.getX()+pp2.getX())/2, pp1.getY() )
pp4 = Point( pp3.getX(), pp1.getY()-30 )
pp5 = Point( (pp1.getX()+pp2.getX())/3, pp1.getY() )
pp6 = Point( (pp1.getX()+pp2.getX())*2/3, pp1.getY() )
#--- define the body ---
self.body = Rectangle( pp1, pp2 )
P1 = pp1
P2 = pp2
#--- the wheels ---
w =abs( P2.getX()-P1.getX() )
h =abs( P2.getY()-P1.getY() )
center1 = Point( w/4+P1.getX(), P2.getY() )
center2 = Point( P2.getX()-w/4, P2.getY() )
r2 = w/8
r1 = r2/2
self.w1 = Wheel( center1, r1, r2 )
self.w2 = Wheel( center2, r1, r2 )
#--- adjust speed if equal to 0 ---
if dx==0: dx = 2
self.dx = dx
#--- put windshield in direction of motion ---
if dx < 0:
self.shield = Polygon( pp1, pp3, pp4 )
else:
self.shield = Polygon( Point( pp2.getX(), pp1.getY() ), pp3, pp4 )
#--- create door ---
self.door = Rectangle( pp5, center2 )
def setFill( self, bodyc, tirec, insidec ):
"""set the color of the body, and tires"""
self.body.setFill( bodyc )
self.shield.setFill( "lightblue" ) # windshield color fixed
self.door.setFill( bodyc )
self.w1.setFill( tirec, insidec )
self.w2.setFill( tirec, insidec )
def draw( self, win ):
"""draw all the parts, in the right order so that wheel overlap everything"""
self.body.draw( win )
self.door.draw( win )
self.shield.draw( win )
self.w1.draw( win )
self.w2.draw( win )
def move( self ):
"""move the car by its own speed"""
self.body.move( self.dx, 0 )
self.shield.move( self.dx, 0 )
self.door.move( self.dx, 0 )
self.w1.move( self.dx, 0 )
self.w2.move( self.dx, 0 )
def IsOutside(self, W, H):
"""returns True if car is outside the window defined by W and H"""
#--- get leftmost and rightmost points ---
P1 = self.body.getP1()
P2 = self.body.getP2()
leftX = P1.getX()
rightX = P2.getX()
#--- are they out? ---
if leftX > W or rightX < 0:
return True
return False
#----------------------------------------------------------------
def waitForClick( win, message ):
""" waitForClick: stops the GUI and displays a message.
Returns when the user clicks the window. The message is erased."""
# wait for user to click mouse to start
startMsg = Text( Point( win.getWidth()/2, win.getHeight()/2 ), message )
startMsg.draw( win ) # display message
win.getMouse() # wait
startMsg.undraw() # erase
#----------------------------------------------------------------
def main():
"""demo program for a list of moving cars"""
global W, H
win = GraphWin( "wheel demo", W, H )
waitForClick( win, "click to start" )
#--- build a list of cars ---
List = []
for i in range( 3 ):
car = Car( Point( 120, 60+i*50 ), Point( 20, 20+i*50 ), 3-random.randrange( 6 ) )
car.setFill( "red", "yellow", "black" )
car.draw( win )
List.append( car )
waitForClick( win, "click to move" )
#--- move the cars ---
while True:
#--- as we move the cars, count the number of outside cars ---
outsideCount = 0
for car in List:
car.move( )
if car.IsOutside(W, H):
outsideCount += 1
#--- if they are all outside, we quit ---
if outsideCount >= len(List):
break
waitForClick( win, "click to end" )
main()
</showafterdate>