# Week 11, Monday: Classes

• Quiz 5 this Friday (searching and sorting)
• Lab 9 program due this Saturday

### 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

Way 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.

### 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, 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):
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:

• the `__init__` method is called the constructor
• the constructor can have 1 or more parameters (the above has 4)
• 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", 11, "Philadelphia 76ers")`)
• 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 `basketball.py`, we could use it in other programs by adding `from basketball import *` to the other programs

### that `self` parameter

Notice 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
>>> 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
``````

### 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 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
``````