Week 12: Classes
Monday
Objects
So far we have used lots of objects and their methods. Remember an object consists of both data and methods (functions). Here are some examples of objects we have used:
-
str objects: data = characters in the string; methods:
lower()
,split()
,isalpha()
, etc -
list objects: data = item in the list; methods:
sort()
,append()
,pop()
, etc -
Zelle Circle objects: data = radius, center point; methods:
draw()
,move()
,setFill()
, etc
example object: NBAPlayer
If we were storing statistics for NBA basketball players, we could use parallel lists: a list of player names, a list of points-per-game for each player, and a list of games played for each player:
players = ["Vladimir Radmanovic","Lou Williams","Lebron James", "Kevin Durant","Kobe Bryant","Kevin Garnett"] ppg = [0, 11.5, 30.3, 28.5, 30.0, 19.2] # points-per-game games = [2, 13, 23, 20, 12, 20] # games played
for i in range(len(players)): print("%d: %20s %4.1f %2d" % (i, players[i], ppg[i], games[i]))
This works but is klunky. And it would be worse if we tried to store more stats (rebounds, assists, blocked shots, etc) and more players (400+ for the NBA).
A better approach would be to create a custom object (an NBAPlayer object) and store
all the data for that player in that object. Then we could just have one list of
NBAPlayer objects in our program. We could also write custom methods to use on those objects — anything
we think we might need, such as a ppg()
method to calculate a player’s average points-per-game,
or a playGame()
method to record new data when a player plays another game.
Here’s an example of how this class could be used:
>>> from nbaplayer import * >>> p1 = NBAPlayer("Jeff Knerr", "phi") >>> print(p1) Jeff Knerr -- Team: phi GP: 0, PTS: 0, PPG: 0.0 >>> print(p1.ppg()) 0 >>> p1.playGame(40) >>> print(p1) Jeff Knerr -- Team: phi GP: 1, PTS: 40, PPG: 40.0 >>> p1.playGame(20) >>> print(p1) Jeff Knerr -- Team: phi GP: 2, PTS: 60, PPG: 30.0 >>> print(p1.ppg()) 30.0
Notice how new objects are constructed: given a name and a team name.
When the player object is first constructed and printed, the stats are initially zero.
And after the playGame()
method is called twice (Jeff plays two good games!), the stats have changed.
syntax of classes
To create the custom object above, we will need to define a class. Think of the class definition as a template: you want to create custom objects, and the class definition says how they are to be constucted, what data is stored in each object, and what methods can be applied to these objects. Once you have your class definition, you can create as many objects of that type as you want.
Here is some of the NBAPlayer
class used above — we will look at what it all means below.
class NBAPlayer(object):
"""class for single NBA player object"""
def __init__(self, name, team, gp=0, pts=0):
"""constructor for player object, given name, etc"""
self.name = name
self.team = team
self.gp = gp # games played
self.pts = pts # total points scored
def __str__(self):
"""
create string with info about this object.
if you print one of these objects, this is how it will look.
"""
s = "%20s -- Team: %3s" % (self.name, self.team)
s += " GP: %3d, PTS: %3d, PPG: %4.1f" % (self.gp, self.pts, self.ppg())
return s
def playGame(self, points):
"""example of adding data to player object"""
self.gp += 1
self.pts += points
def ppg(self):
"""calculate player's average points per game"""
if self.gp == 0:
return 0
else:
ave = self.pts/float(self.gp)
return ave
Notes on the above class definition:
-
the
__init__
method is called the constructor -
the constructor can have 1 or more parameters (the above has 5, but one is special)
-
the first parameter in all of the methods is self, and this refers back to whatever object we are applying the method to (more about this below)
-
in a program that uses this class, the constructor is called by using the name of the class (e.g.,
p1 = NBAPlayer("Jeff Knerr", "phi")
) -
all
self.whatever
variables (called instance variables) in the constructor are the data stored in each object -
the
__str__
method is used whenever an object is printed (e.g.,print(p1)
) -
the
__str__
method should return a string (not print a string) -
when you design a new object, you can write the constructor to have as many parameters as you want, have as many instance variables as you need, have the object print however you want (using the
__str__
method), and have as many other methods as you see fit! -
if this were in a file called
nbaplayer.py
, we could use it in other programs by addingfrom nbaplayer import *
to the other programs
that self
parameter
Notice that the constructor above has 5 parameters (self, name, team, gp=0, pts=0), but
when it is called in the program (p1 = NBAPlayer("Jeff Knerr", "phi")
),
only 2 arguments are used. The argument that corresponds to the self
parameter is
always implied. The self
parameter simply refers back to which object we are talking
about (e.g., p1
).
Also, the last two parameters (gp=0, pts=0) are a way to include
default arguments. When constructing objects, if they are specified,
they will have those values (ex: p1 = NBAPlayer("Jeff","phi",10,200)
),
and if not specified, they will have the default values (in this case, both zero).
If self
is confusing to you, you are not alone! For now, just make sure all methods in the
class definition have self
as their first parameter.
Also notice, any self.whatever
variable created in __init__
can be used in all
other methods in the class, without being passed as parameters. For example, the playGame()
method updates self.gp
and self.pts
.
adding a method
Suppose we want to add some additional functionality to our NBAPlayer
class (maybe we
are creating the software behind nba.com or espn.com/nba). Players are often traded from
one team to another, so we would like to be able to do something like this:
>>> from nbaplayer import * >>> p1 = NBAPlayer("Jeff Knerr", "phi") >>> p1.playGame(20) >>> p1.playGame(10) >>> p1.playGame(3) # Jeff not playing well....let's trade him >>> p1.trade("bos") >>> print(p1) Jeff Knerr -- Team: bos GP: 3, PTS: 33, PPG: 11.0
Can you add the trade()
method to the above class? As used above, it has
one argument (the new team), so the method should have two parameters: self
and some variable to hold the value of the new team (maybe newteam
??).
And the only thing this method does is change the value of the self.team
instance variable.
Here’s the new method:
def trade(self, newteam):
"""change team of player"""
self.team = newteam
adding a new instance variable
What needs to change if we want to keep track of another statistic, such as number of rebounds?
That requires another instance variable (self.rebounds
) in the constructor, as well as
modifying the playGame()
method (add a rebounds
parameter, and update self.rebounds
).
And like ppg()
, we might want to make a whole new method (rpg()
?) to calculate and
return the average rebounds-per-game. You may also want to change the __str__
method to
include the rebounding stats.
write the Cake(..)
class
Write a Cake
class that works with the following test code:
>>> from cake import * >>> c1 = Cake("Chocolate") >>> c1.slice(8) >>> print(c1) Chocolate cake (slices left: 8). >>> c2 = Cake("Carrot") >>> c2.slice(12) >>> print(c2) Carrot cake (slices left: 12). >>> c1.serve() Here's your slice of Chocolate cake! >>> print(c1) Chocolate cake (slices left: 7).
And make sure your cake class accounts for running out of slices!
>>> for i in range(10): ... c1.serve() ... Here's your slice of Chocolate cake! Here's your slice of Chocolate cake! Here's your slice of Chocolate cake! Here's your slice of Chocolate cake! Here's your slice of Chocolate cake! Here's your slice of Chocolate cake! Here's your slice of Chocolate cake! No Chocolate cake left! :( No Chocolate cake left! :( No Chocolate cake left! :(
Wednesday
finish the cake class
What if we want to prevent slicing the cake twice? Can you check to see if the cake was already sliced??
>>> from cake import * >>> c = Cake("Carrot") >>> c.slice(8) >>> c.slice(10) You already sliced this cake!
One way to do this is to add a new instance variable, to keep track of if the cake has already been sliced.
Here’s an example of the full Cake
class:
class Cake(object):
"""cake class"""
def __init__(self, flavor):
"""constructor for cake objects, given flavor"""
self.flavor = flavor
self.slices = 0
self.sliced = False # initially, cakes have not been sliced
def __str__(self):
"""must RETURN A STRING!!!"""
if self.sliced:
s = "%s cake (slices left: %d)." % (self.flavor,self.slices)
else:
s = "%s cake (unsliced)." % (self.flavor)
return s
def slice(self, nslices):
"""cut the cake into nslices"""
if self.sliced:
print("You already sliced this cake!")
elif nslices<1 or nslices>50:
print("That's a crazy number of slices!")
else:
self.slices = nslices
self.sliced = True
def serve(self):
"""serve out one slice of cake"""
if self.slices > 0:
print("Here's your slice of %s cake!" % self.flavor)
self.slices -= 1
else:
print("No %s cake left! :( " % self.flavor)
def getFlavor(self):
"""getter for flavor data"""
return self.flavor
def getSlices(self):
"""getter for slice data"""
return self.slices
zturtle class
Turtle graphics is another way to draw things, but instead of graphics objects (like Circles and Rectangles), we control a pen (or a turtle), and tell it to move forward or change it’s heading. It’s called turtle graphics because, like a turtle walking across the sand, it leaves a trail.
Here’s and pseudo-code example of drawing a square using turtle graphics:
create a turtle object for i in range(4): move forward (and draw) a certain amount turn by 90 degrees
Let’s create a turtle object using the Zelle Graphics Library.
If you run update21
, you should get a zturtle.py
file.
That file includes a ZTurtle
class with a contructor and the
forward(..)
function. Your job is to finish the Zturtle class — write
any of the methods below that aren’t already done (see the zturtle.py
file). Also, see testturtle.py
for an example of how the ZTurtle
object is used. And start by writing the methods that are needed in that
file (turn(degrees)
and setColor(color)
).
Here’s the full documentation for the ZTurtle
class:
class ZTurtle(object) | Zelle Graphics Turtle | | __init__(self, x, y, window) | construct new turtle object with given x,y position | | __str__(self) | return a string with turtle info (x,y,heading,tail status) | | down(self) | lower the tail | | forward(self, ds) | move forward a distance ds, draw if tail is down | | getHeading(self) | return the current heading | | getX(self) | return the current x coordinate | | getY(self) | return the current y coordinate | | moveto(self, x, y) | move turtle, without drawing, to given x,y location | | setColor(self, color) | set the drawing color | | setHeading(self, h) | set heading to given heading h | | turn(self, amount) | change the heading by the given amount (degrees) | | up(self) | raise the tail
Once you have the zturtle class working, edit/try a few of these programs (they each have starter code in them, you just need to finish them):
testturtle.py
spiral.py
boxspiral.py
star.py
walk.py
tree.py
koch.py
Friday
No class — Thanksgiving Break!
Swatify Music App
Suppose we are writing a music app that has songs and playlists. We want the user to be able to make and edit playlists of songs, and, obviously, play the songs on the playlist.
Here’s a simplified example of how the objects could be used
(see swatify.py
):
from song import *
from playlist import *
def main():
p1 = Playlist("Jeff's Awesome Playlist")
# show initial, empty playlist
print(p1)
# read in song data, add some songs to playlist
songs = readFile("songs.csv")
p1.add(songs[0])
p1.add(songs[2])
p1.add(songs[4])
print(p1)
# toggle the shuffle, play the playlist
p1.toggleShuffle()
p1.play()
def readFile(fn):
"""read in song data from file, one song per line"""
#title, running time, artist, date-of-purchase, number-of-plays
#Badlands,4:04,Bruce Springsteen,9/14/05; 10:00 PM,60
songs = []
inf = open(fn,"r")
for line in inf:
data = line.strip().split(",")
title = data[0]
rtime = data[1]
artist = data[2]
dop = data[3]
nplays = data[4]
# all song objects have a song title, artist, running time, and number of plays
s = Song(title,artist,rtime,int(nplays))
songs.append(s)
inf.close()
return songs
main()
And here is the output of the above code (just an example of how it might look):
$ python3 swatify.py Playlist: Jeff's Awesome Playlist -- total running time: 0hr:0min:0sec [shuffle=False, public=True] No songs in this playlist... Playlist: Jeff's Awesome Playlist -- total running time: 0hr:10min:25sec [shuffle=False, public=True] 1: The A Team by Ed Sheeran 2: Almost Like Being In Love by Frank Sinatra 3: Badlands by Bruce Springsteen playing Badlands...by Bruce Springsteen playing The A Team...by Ed Sheeran playing Almost Like Being In Love...by Frank Sinatra
We’ll start by making the Song and Playlist classes.
the Song(..)
class
Each Song object should have these instance variables:
-
title: title of the song
-
artist: song artist
-
running time: length of the song — a string, like '3:05'
-
number of plays: how many times the song has been played (an integer)
Here’s the full documentation for the Song class:
class Song(builtins.object) | Song object class | | Methods defined here: | | __init__(self, title, artist, rtime, nplays) | constructor, given title, artist, running time, number of plays | | __str__(self) | should return a string with song info | | getArtist(self) | getter for song artist | | getPlays(self) | getter for number of times song has been played | | getRTime(self) | getter for running time of song (string, like '4:02') | | getSeconds(self) | getter for running time of song in seconds (int, like 242) | | getTitle(self) | getter for title of song | | play(self) | dummy 'play' method, just says playing song and calls sleep(1) | (also increments number of plays)
If you look in the songs.csv
file, you’ll see each song in there has a
title, a running time (a string, like '5:37'), an artist, a
date-of-purchase (which we are not using), and a number (integer) for
how many times the song has been played.
Your job is to add the above class and methods to song.py
, so we can
create Song objects from the (fake) data in songs.csv.
Here is some test code you could use to make sure your Song class is working:
s = Song("A","B","4:30",10)
print(s)
s.play()
print(s)
print(s.getTitle())
print(s.getArtist())
print(s.getRTime())
print(s.getPlays())
print(s.getSeconds())
And running it would show this:
$ python3 song.py A by B (4:30, 10) playing A...by B A by B (4:30, 11) A B 4:30 11 270
The only tricky method is getSeconds()
which returns the running time
as an integer number of seconds. In the above example, the running time
is '4:30', which getSeconds()
uses to calculate and return the total
number of seconds (270).
the Playlist(..)
class
Once you have the Song
class working, write the playlist class.
The user should be able to create a new playlist, given the title of the playlist. They should also be able to add songs to the playlist. All Playlist objects should have the following instance variables:
-
title: title of the playlist
-
songs: list of songs in the playlist (initially empty)
-
time: running time of full playlist (all songs) in seconds (initially 0)
-
public: boolean for playlist status (defaults to True (public playlist))
-
shuffle: boolean for playing the songs (initially False, so play in order)
Here’s the documentation for the Playlist objects:
class Playlist(builtins.object) | Playlist class | | Methods defined here: | | __init__(self, title) | constructor for Playlist objects, given title of playlist | | __str__(self) | should return a string with playlist info | | add(self, song) | add given song to the playlist | | play(self) | play the playlist (call each song's play method) | | toggleShuffle(self) | toggle the shuffle instance variable (boolean)
And here’s some test code to check your playlist objects:
p1 = Playlist("Best Playlist Ever")
print(p1)
s = Song("A","B","4:30",10)
p1.add(s)
p1.add(s)
print(p1)
and the output (depending on how you wrote your str method):
Playlist: Best Playlist Ever -- total running time: 0hr:0min:0sec [shuffle=False, public=True] No songs in this playlist... Playlist: Best Playlist Ever -- total running time: 0hr:9min:0sec [shuffle=False, public=True] 1: A by B 2: A by B
the swatify app
Once you have your song and playlist objects working, try the
swatify.py
app to make sure it works.
if you have time
Add a menu() function to the swatify app to give the user 4 options: add, show, play, quit. Here’s one example of how this could work:
$ python3 swatify.py add|show|play|quit What do you want to do? show Playlist: Jeff's Awesome Playlist -- total running time: 0hr:0min:0sec [shuffle=False, public=True] No songs in this playlist... add|show|play|quit What do you want to do? add 1. The A Team by Ed Sheeran 2. A.M. Radio by Everclear 3. Almost Like Being In Love by Frank Sinatra 4. Animal Song by Loudon Wainwright III 5. Badlands by Bruce Springsteen 6. Breakdown by Tom Petty & The Heartbreakers 7. Do You Feel by The Rocket Summer 8. Everyday Superhero by Smash Mouth 9. Gonna Fly Now by Bill Conti 10. It's On by Superchick 11. Like a Rolling Stone by Bob Dylan 12. Message In A Bottle by The Police 13. Since U Been Gone by Kelly Clarkson 14. Sweet Caroline by Glee Cast 15. Take It Easy by Eagles 16. This Is The Life by Hannah Montana 17. Welcome To The Machine by Pink Floyd 18. You Get What You Give by New Radicals Which song do you want to add? 5 add|show|play|quit What do you want to do? add 1. The A Team by Ed Sheeran 2. A.M. Radio by Everclear 3. Almost Like Being In Love by Frank Sinatra 4. Animal Song by Loudon Wainwright III 5. Badlands by Bruce Springsteen 6. Breakdown by Tom Petty & The Heartbreakers 7. Do You Feel by The Rocket Summer 8. Everyday Superhero by Smash Mouth 9. Gonna Fly Now by Bill Conti 10. It's On by Superchick 11. Like a Rolling Stone by Bob Dylan 12. Message In A Bottle by The Police 13. Since U Been Gone by Kelly Clarkson 14. Sweet Caroline by Glee Cast 15. Take It Easy by Eagles 16. This Is The Life by Hannah Montana 17. Welcome To The Machine by Pink Floyd 18. You Get What You Give by New Radicals Which song do you want to add? 10 add|show|play|quit What do you want to do? show Playlist: Jeff's Awesome Playlist -- total running time: 0hr:7min:32sec [shuffle=False, public=True] 1: Badlands by Bruce Springsteen 2: It's On by Superchick add|show|play|quit What do you want to do? play playing Badlands...by Bruce Springsteen playing It's On...by Superchick add|show|play|quit What do you want to do? quit $
if you have lots of time
What other features would you add to this app and the classes? Here are some ideas:
-
delete(song) method for the playlist class (allow user to delete a song from a playlist)
-
edit(playlist) function for the swatify app (allow user to select songs to move around or delete, or add more songs)
-
toggle method for the
self.public
instance variable in the playlist class -
add a search(string) function to swatify app to allow user to search for songs (e.g., show all songs by a given artist), then have the option of adding them to a playlist
make it really play sound
Let’s add a real play()
method to the Song class!
I put a new songs.csv
file and some mp3 files in
/scratch/knerr/swatify
.
You can see these with this command: ls /scratch/knerr/swatify
And you can see the songs.csv file with this (in a terminal):
cat /scratch/knerr/swatify/songs.csv
If you do that, you’ll notice an additional item on each line: an mp3
file. We need to modify the jk_song.py
file to add this additional item:
-
add an extra parameter to __init__:
def __init__(self,title,artist,rtime,nplays,audiofile):
-
and add an extra instance variable to init:
self.audiofile = audiofile
-
add "import subprocess" at the top of the file (so we can call unix commands from python)
-
change the
play()
method to do this instead of sleeping:subprocess.call("mplayer %s" % self.audiofile, shell=True)
The subprocess.call()
line just runs the mplayer
command on the
given audiofile (just like we did on the command line).
Now try running jk_swatify.py
to see if it works
Also note, if you try this on other lab computers, sound may or may not work. Here’s a help page we have if you are trying to get sound to work on the CS lab machines (see the "Room 256" section): audio help page