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:
lower()
, split()
, isalpha()
, etcsort()
, append()
, pop()
, etcdraw()
, move()
, setFill()
, etcWay back in Week 2 we looked at a basketball stats example, where we had data for each basketball player stored in 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 basketball import *
>>> p1 = NBAPlayer("Jeff Knerr", 11, "Philadelphia 76ers")
>>> print(p1)
Jeff Knerr #11 -- Team: Philadelphia 76ers
GP: 0, PTS: 0
>>> print(p1.ppg())
0
>>> p1.playGame(35)
>>> print(p1)
Jeff Knerr #11 -- Team: Philadelphia 76ers
GP: 1, PTS: 35
>>> p1.playGame(30)
>>> print(p1)
Jeff Knerr #11 -- Team: Philadelphia 76ers
GP: 2, PTS: 65
>>> print(p1.ppg())
32.5
Notice how new objects are constructed: given a name, a jersey number, 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.
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, number, team):
"""constructor for player object, given name, etc"""
# any self.whatever variable is DATA for this object,
# and can be used in any methods in this class
self.name = name
self.number = int(number) # jersey number
self.team = team
self.gp = 0 # games played
self.pts = 0 # points scored
def __str__(self):
"""pretty-print info about this object"""
s = "%15s #%i -- Team: %20s" % (self.name, self.number, self.team)
s += "\n\t\tGP: %3d, PTS: %3d" % (self.gp, self.pts)
return s
def playGame(self, points):
"""example of adding data to player object"""
self.gp += 1
self.pts += points
def ppg(self):
"""calculate 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:
__init__
method is called the constructorp1 = NBAPlayer("Jeff Knerr", 11, "Philadelphia 76ers")
)self.whatever
variables (called instance variables) in the constructor are
the data stored in each object__str__
method is used whenever an object is printed (e.g., print(p1)
)__str__
method should return a string (not print a string)__str__
method),
and have as many other methods as you see fit!basketball.py
, we could use it in other programs
by adding from basketball import *
to the other programsself
parameterNotice that the constructor above has 4 parameters (self, name, number, team), but
when it is called in the program (p1 = NBAPlayer("Jeff Knerr", 11, "Philadelphia 76ers")
),
only 3 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
).
If this 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
.
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 basketball import *
>>> p1 = NBAPlayer("Jeff Knerr", 11, "Washington Bullets")
>>> p1.playGame(20)
>>> p1.playGame(10)
>>> p1.playGame(3) # Jeff not playing well....let's trade him
>>> p1.trade("New York Knicks")
>>> print(p1)
Jeff Knerr #11 -- Team: New York Knicks
GP: 3, PTS: 33
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
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 a Pizza
class that works with the following test code:
p1 = Pizza("cheese")
p2 = Pizza("mushroom and onion")
print(p1)
print(p2)
print("-"*20)
print("Num slices left in p2: %s" % p2.getSlices())
print("Eating a slice of %s!" % p2.getTopping())
p2.eatSlice()
print(p2)
print("-"*20)
for i in range(10):
print("Eating a slice of %s!" % p1.getTopping())
p1.eatSlice()
print(p1)
And gives the following output:
cheese pizza :: slices left = 8
mushroom and onion pizza :: slices left = 8
--------------------
Num slices left in p2: 8
Eating a slice of mushroom and onion!
mushroom and onion pizza :: slices left = 7
--------------------
Eating a slice of cheese!
cheese pizza :: slices left = 7
Eating a slice of cheese!
cheese pizza :: slices left = 6
Eating a slice of cheese!
cheese pizza :: slices left = 5
Eating a slice of cheese!
cheese pizza :: slices left = 4
Eating a slice of cheese!
cheese pizza :: slices left = 3
Eating a slice of cheese!
cheese pizza :: slices left = 2
Eating a slice of cheese!
cheese pizza :: slices left = 1
Eating a slice of cheese!
cheese pizza :: slices left = 0
Eating a slice of cheese!
No slices left... :(
cheese pizza :: slices left = 0
Eating a slice of cheese!
No slices left... :(
cheese pizza :: slices left = 0