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
Pizza
classHere is the pizza class from above:
class Pizza(object):
def __init__(self, topping):
"""constructor for pizza class...requires topping string"""
self.topping = topping
self.slices = 8 # defaults to 8 slices
def __str__(self):
"""should create and return a string from instance vars"""
s = "%s pizza :: slices left = %d" % (self.topping, self.slices)
return s
def eatSlice(self):
"""removes one slice from the pizza"""
if self.slices > 0:
self.slices -= 1
else:
print("No slices left... :(")
def getSlices(self):
"""getter for number of slices"""
return self.slices
def getTopping(self):
"""getter for topping"""
return self.topping
__init__
method, used to construct new objects of a given classstr
method: used when you print an object (should return a string)self.whatever
variable in __init__
)getSlices()
above)trade()
from the NBAPlayer
class)Suppose we want to simulate a horse race:
--------------------------------------------------
Albatross
Man O'War
Seabiscuit
Smarty Jones
--------------------------------------------------
And they're off!
--------------------------------------------------
HH Albatross
H Man O'War
HHH Seabiscuit
HHHH Smarty Jones
--------------------------------------------------
HHH Albatross
HHHH Man O'War
HHHH Seabiscuit
HHHHHH Smarty Jones
--------------------------------------------------
HHHHH Albatross
HHHHHH Man O'War
HHHHHH Seabiscuit
HHHHHHHHHH Smarty Jones
--------------------------------------------------
HHHHHHHHH Albatross
HHHHHHHHHH Man O'War
HHHHHHHHHH Seabiscuit
HHHHHHHHHHHH Smarty Jones
and so on...
An obvious object in this program is the horse. Each horse has a name and a position in the race. To simulate a race, horses move, adding random numbers to their positions. Let's write the Horse class:
from random import randrange
###########################################
class Horse(object):
"""horse class for racing app"""
def __init__(self, name):
"""constructor for horse objects"""
self.name = name
self.posn = 0 # all horses start at the startng line
def __str__(self):
"""should return a string representation of Horse object"""
return "Name: %14s is @ position %3d" % (self.name, self.posn)
def move(self):
"""move the horse a random amount"""
self.posn += randrange(1,5)
def getName(self):
"""getter for name of horse"""
return self.name
def getPosition(self):
"""getter for position of horse"""
return self.posn
###########################################
If the above code is in a file called horse.py
, we can either test
it using the python interactive shell:
>>> from horse import *
>>> h1 = Horse("jeff")
>>> print(h1)
Name: jeff is @ position 0
>>> for i in range(5):
... h1.move()
... print(h1)
...
Name: jeff is @ position 1
Name: jeff is @ position 2
Name: jeff is @ position 3
Name: jeff is @ position 7
Name: jeff is @ position 10
Or we can add a section of test code at the bottom of horse.py
:
def main():
"""test code goes here"""
h1 = Horse("Barbaro")
print(h1)
h2 = Horse("Mr. Ed")
print(h2)
assert(h2.getName() == "Mr. Ed")
assert(h2.getPosition() == 0)
for i in range(5):
h1.move()
h2.move()
print(h1)
print(h2)
assert(h1.getPosition() > 0)
assert(h2.getPosition() > 0)
if __name__ == "__main__":
main()