Week 12: classes and objects
Monday
The video from today’s lecture (Monday, April 20, 2020):
And here are the annoucements:
-
Lab 10 is up and due Saturday (Apr 25)
-
Quiz 5 is out and due Friday
This week and next are all about Object-Oriented Programming (OOP) and creating our own objects!
the class file
Look at restaurant.py
for example of a class file.
This file defines the Restaurant object data and methods.
Also see rapp.py
for example of using the Restaurant class.
class Restaurant(object):
"""restaurant class"""
def __init__(self,name,city,state,stars,reviewcount):
"""constructor for Restaurant objects"""
# these are the instance variables
self.name = name
self.city = city
self.state = state
self.stars = stars
self.reviewcount = reviewcount
# every restaurant object contains these data
def getName(self):
"""getter for name of restaurant"""
return self.name
def getCity(self):
"""getter for city of restaurant"""
return self.city
def getState(self):
"""getter for state of restaurant"""
return self.state
def getStars(self):
"""getter for stars of restaurant"""
return self.stars
def getReviewCount(self):
"""getter for review counts of restaurant"""
return self.reviewcount
# NOTE: for **all** methods, self is the first parameter.
# self refers back to *which* object we are operating on
def setStars(self,s):
"""setter for stars of restaurant"""
self.stars = s
def setReviewCount(self,rc):
"""setter for review counts of restaurant"""
self.reviewcount = rc
Lots of new syntax and terminology to learn!
-
class definition
-
constructor (the init method)
-
instance variables (the self.whatever variables in init)
-
the "self" parameter (refers to the object)
-
getters and setters
-
class WRITER vs class USER: in this example, the person writing the
rapp.py
program is the class USER
playing card games
If you were going to write a card game program like solitaire or poker, what objects would you include in your application (game)?
Two possible objects are a Card
object to represent each card in the
game, and a Deck
object to represent a deck of playing cards.
I’ve already written a version of the Card
class. Let’s look at that and
then write the Deck
class together.
Here is the Card
class in card.py
:
class Card(object):
"""card object for playing cards"""
def __init__(self, rank, suit):
"""create playing card, given rank and suit"""
self.rank = rank # A23456789TJQK
self.suit = suit # CDHS
def __str__(self):
"""return string representation of playing card"""
return self.rank+self.suit
def getRank(self):
"""getter for card rank"""
return self.rank
def getSuit(self):
"""getter for card suit"""
return self.suit
def main():
"""test code here"""
cards = []
for rank in "A23456789TJQK":
c = Card(rank,"H") # all the hearts
cards.append(c)
for c in cards:
print(c)
if __name__ == "__main__":
main()
In the above, each card object has rank (ace,two,three..) and suit
(hearts,clubs…). The test code at the bottom of the file shows an
example of creating a card object (e.g., c = Card("4","H")
). Besides
the constructor, the only other methods are getters for the rank and
suit, and the str method, which gets automatically called when we try
to print card objects.
Now that we have Card
objects, let’s make a deck of cards!
In deck.py
create the Deck
class. Each deck of cards should
contain 52 playing cards (i.e., a list of Card
objects).
Here’s one way to write the constructor for the Deck
class:
def __init__(self):
"""constructor for deck of cards"""
self.cards = []
for suit in "CDHS": # clubs, diamonds,...
for rank in "A23456789TJQK":
c = Card(rank,suit)
self.cards.append(c)
Can you add these other methods to the deck class?
__str__ returns string representation of Deck object shuffle() shuffles the cards in the deck dealCard() removes a card from the deck and returns it
Wednesday
The video from today’s lecture (Wed, April 22, 2020):
-
-
don’t forget: quiz 5 is this week (Mon-Fri)
-
Lab 10 due Saturday
-
more Deck
class
Let’s add the shuffle and dealCard methods to the Deck
class,
and then test everything.
def shuffle(self):
"""shuffle the deck of cards"""
for i in range(len(self.cards)):
# pick a random index (ri)
ri = randrange(len(self.cards))
self.cards[i],self.cards[ri] = self.cards[ri],self.cards[i]
def dealCard(self):
"""deal/return one card from the deck"""
card = self.cards.pop()
return card
So now if you create a deck and deal out some cards, you should see that many fewer cards in the deck when you print the deck:
def main():
"""test code for deck class"""
d = Deck()
print(d)
d.shuffle()
print(d) # cards should be shuffled
for i in range(5):
c = d.dealCard()
print(c)
print(d) # deck should have 5 less cards in it
writing a card game
I made a simple bridge.py
file that uses the Card
and Deck
classes
(as well as a Hand
class). My goal was to show how all of the classes
are imported and used, if we were going to write a real game to play
contract bridge. I didn’t
write the full game, but you should be able to see how I shuffle the
deck of cards and then deal out 4 hands.
the Cake
class
Your turn…make a Cake class! I want to be able to use the cake objects like this:
c1 = Cake("chocolate") # create a chocolate cake c2 = Cake("carrot") # create a carrot cake print(c1) # says it's a chocolate cake, unsliced print(c2) # says it's a carrot cake, unsliced c1.slice(8) # cut the chocolate cake into 8 slices c2.slice(16) # cut the carrot cake into 16 slices print(c1) # says it's a chocolate cake with 8 slices left print(c2) # says it's a carrot cake with 16 slices left for i in range(10): c1.serve() # serves one of the chocolate cake slices print(c1)
What data and methods do all Cake objects need? Here’s the output
I am looking for, from running the above code. See if you can write
the cake.py
file!
chocolate cake has not been sliced yet carrot cake has not been sliced yet chocolate cake -- slices left: 8 carrot cake -- slices left: 16 Here's your slice of chocolate cake! chocolate cake -- slices left: 7 Here's your slice of chocolate cake! chocolate cake -- slices left: 6 Here's your slice of chocolate cake! chocolate cake -- slices left: 5 Here's your slice of chocolate cake! chocolate cake -- slices left: 4 Here's your slice of chocolate cake! chocolate cake -- slices left: 3 Here's your slice of chocolate cake! chocolate cake -- slices left: 2 Here's your slice of chocolate cake! chocolate cake -- slices left: 1 Here's your slice of chocolate cake! chocolate cake -- slices left: 0 Sorry, no cake left! :( chocolate cake -- slices left: 0 Sorry, no cake left! :( chocolate cake -- slices left: 0
Friday
The video from today’s lecture (Friday, April 24, 2020):
And here are the annoucements:
-
Lab 10 is up and due Saturday (Apr 25)
-
Quiz 5 is out and due Friday
-
Lab 11 will be posted on Sunday
-
Final exam is now just Quiz 6, on recursion and classes/objects
more cake
Last time we were writing the Cake
class.
Here’s what I wrote:
class Cake(object):
"""Cake class"""
def __init__(self, flavor):
"""create cake of given flavor"""
self.flavor = flavor
self.sliced = False # boolean to tell if cake has been sliced
self.slices = None # cake hasn't been sliced yet
# hmmm...now that I look at that, I could
# probably just use self.slices==None to determine
# if the cake has been sliced or not...oh well.
def __str__(self):
"""should return a string"""
s = "%s cake" % (self.flavor)
if self.sliced:
s += " -- slices left: %d" % (self.slices)
else:
s += " has not been sliced yet"
return s
def slice(self, nslices):
"""cut cake into n slices"""
if not self.sliced:
self.slices = nslices
self.sliced = True
def serve(self):
"""serve out a slice of the cake"""
if self.sliced:
if self.slices > 0:
print("Here's your slice of %s cake!" % (self.flavor))
self.slices = self.slices - 1
else:
print("Sorry, no cake left! :( ")
else:
print("Cake hasn't been sliced yet!")
def main():
c1 = Cake("chocolate") # create a chocolate cake
c2 = Cake("carrot") # create a carrot cake
print(c1) # says it's a chocolate cake, unsliced
print(c2) # says it's a carrot cake, unsliced
c1.slice(8) # cut the chocolate cake into 8 slices
c2.slice(16) # cut the carrot cake into 16 slices
print(c1) # says it's a chocolate cake with 8 slices left
print(c2) # says it's a carrot cake with 16 slices left
for i in range(10):
c1.serve() # serves one of the chocolate cake slices
print(c1)
if __name__ == "__main__":
main()
adding assert()
statements
The above works, and I can look at the output from main()
and check
that everything is the way it should be. It would be nice to have the
computer automatically check stuff (make the computer do the work!), so
I don’t have to scan all of the outputs from print()
statements.
The assert(..)
statement is one way to automate the testing!
Suppose I want to test that my serve()
method is working correctly,
and doesn’t serve out more slices of cake than we have. If I add this
getter for the number of slices:
def getSlices(self):
"""getter for number of slices left"""
return self.slices
Then I can use that and test that it’s giving the numbers I think it should.
Here’s some test code using assert statements:
flavor = "AAAA" # make a fake Cake object
n = 10
fake = Cake(flavor)
assert(fake.getSlices()==None) # this should be True
fake.slice(n)
assert(fake.getSlices()==10) # and so should this
Remember, for assert(…)
, you will see no output if what is inside
the parens evaluates to True
. By writing those asserts, I am saying, if my
code is working properly, those statements should be True
. Now when I run
the test code, no output means all my assert tests passed. If I get an
AssertionError
, then something must be wrong.
adding the len()
function
For some objects, like strings and lists, using the len()
function makes sense.
For strings it tells us the length of the string, and for lists it tells
us how many items are in the list. For a Restaurant
object, length
doesn’t really mean anything useful, so we wouldn’t want to use the
length function on a Restaurant
object. What about for cakes? Let’s
say the length of a Cake is "how many slices are left". Can we add
something to the Cake
class so we can use len()
on our cake objects?
Here’s what happens when we try to use it now:
TypeError: object of type 'Cake' has no len()
If, however, we add this method to the Cake
class:
def __len__(self):
"""make len() function work for our cakes"""
return self.slices
Now look what happens:
>>> from cake import * >>> yum = Cake("Birthday") >>> yum.slice(12) >>> print(len(yum)) 12 >>> yum.serve() Here's your slice of Birthday cake! >>> print(len(yum)) 11
the Contact
class
Suppose we want to write a Contact
class to help keep track of our contacts.
Each contact could have a name (first and last), email, and phone number.
Here’s a simple contact.py
file:
class Contact(object):
"""Contact Class"""
def __init__(self):
"""contstuctor for empty contact objects"""
self.last = "" # last name
self.first = "" # first name
self.email = ""
self.phone = ""
def __str__(self):
"""should return a string..."""
s = "%s, %s -- %s, %s" % (self.last, self.first,
self.email, self.phone)
return s
def setLast(self, lastname):
"""setter for last name"""
self.last = lastname
def setFirst(self, firstname):
"""setter for first name"""
self.first = firstname
def setEmail(self, email):
"""setter for email"""
self.email = email
def setPhone(self, phone):
"""setter for phone"""
self.phone = phone
For the above, we are assuming contacts will first be created empty, and then all information will be added using the setters:
c = Contact()
c.setLast("Knerr")
c.setFirst("Jeff")
c.setEmail("jknerr1@swat.edu")
c.setPhone("614-690-5758")
print(c)
Maybe we want to make it a little easier to add names, providing the full name as "First Last". Let’s write another method to handle that:
def setName(self, fullname):
"""set first and last, given full name"""
first,last = fullname.split()
self.first = first
self.last = last
Now something like this should work:
c2 = Contact()
c2.setName("George Washington")
print(c2)
Finally, what if we want a way to create that contact, providing the full name to the constructor? Python allows you to specify parameters and a default value, like this:
def __init__(self,fullname=""):
"""contstuctor for contact objects, allows given fullname"""
if fullname == "":
self.last = "" # last name
self.first = "" # first name
else:
self.setName(fullname)
self.email = ""
self.phone = ""
The above says, if fullname
is given, use it in the call to setName(..)
.
If fullname
is not given, then just set the first and last name instance
variables to the empty string.
This makes our Contact
constructor just a little nicer, allowing
two possible ways to call it:
c2 = Contact() # fullname not given
c2.setName("George Washington")
print(c2)
c3 = Contact("Martha Washington") # fullname given, so use it
print(c3)
Either way works now!
Also, note the call to self.setName(..)
in the above constructor.
If you are calling a method from the same class, the syntax
is self.methodname(..)
.