So far in this course we’ve been writing programs using functions and built-in data types. We’ve also created our own abstract data types (such as the link and tree ADTs). Recall that ADTs use constructors and selectors that allow you to create a data type and retrieve information about a data type. This was really all you could do with ADTs; since they are represented purely with functions, they are immutable.
Object oriented programming introduces another paradigm for us to represent and organize our data types. Using OOP, we create classes, which provides an outline for an object. Classes consist of attributes and methods. Attributes allow us to keep track of and, more importantly, modify the state of our objects, something we couldn’t do with ADTs. Methods, as opposed to functions, must be called on an instance. Before we go any further, let’s sort out what all this new vocabulary means!
Terminology
It is very important to fully understand all the terms that are associated with OOP before you attempt to write any code.
object: anything that has attributes and/or performs actions (think real life objects!)
class: a type or category of objects
instance: a single object belonging to some class
instance attribute: an attribute that is specific to each instance of a class
class attribute: an attribute that is the same for all instances of a class
method: an action that a single object can perform; a function that must be called on an instance
Example
Let’s say we wanted to represent puppies as objects. Our class would be called Puppy. If we want two puppies we would create two instances of the Puppy class. Some attributes that are specific to each Puppy are its name and favorite toys. These will be instance attributes. It’ll also be useful to keep track of the state of each puppy using instance attributes, such as a puppy’s hungriness and happiness level. Let’s say we want to keep the total number of puppies that exist. This would be stored as a class attribute, num_puppies, since it is the same for all puppies. Finally, what are our puppies able to do? They should be able to bark, eat, and play. These will be the methods.
If you’re still having trouble differentiating between instance and class attributes, ask yourself this question: if I change this value for one object, must it change for all other objects? In this case, if one puppy eats and decreases her hungriness, that will not directly affect any other Puppy’s hungriness attribute. However, if more puppies are created and num_puppies gets incremented, this value increases for every Puppy in our puppy world.
Now let’s implement it!
Implementation
Below is the implementation of our Puppy class. Read through it and try to figure out what each line does.
classPuppy:"""Class representation of the best creatures to have walked our planet."""num_puppies=0def__init__(self,name,fav_toys):"""Sets the instance attributes. Instance attributes that start
off differently for each instance are passed through as arguments.
Otherwise, they are set to their default values in here. Every
time a Puppy is created, the class attribute num_puppies gets
incremented."""self.name=nameself.fav_toys=fav_toys# list of favorite toys
self.hungriness=0self.happiness=5Puppy.num_puppies+=1defbark(self):"""Prints puppy's greeting. He just met you but he already loves
you!"""print("Woof! My name is {0} and I love you!".format(self.name))defplay(self,toy):"""Puppies can eat if they're not too hungry. Playing will
increase a puppy's happiness (and hungriness)!"""ifself.hungriness<5:iftoyinself.fav_toys:print(toy+" is my favorite toy EVER.")self.happiness+=2else:self.happiness+=1self.hungriness+=1else:print("I am too hungry to play :(")defeat(self,food):"""Puppies will eat if they're hungry. Eating will decrease a
puppy's hungriness and increase its happiness."""ifself.hungriness:print(food+" is my favorite thing to eat EVER.")self.hungriness-=1self.happiness+=1else:print("No thanks! Let's play!")
Test your understanding
What are all the instance attributes of a Puppy?
What is a puppy’s name initialized to? What are its hungriness and happiness levels initialized to?
What is the class attribute? Where does it change? What is it keeping track of?
What common parameter do all methods have?
Does the bark method change the state of the Puppy?
What must be passed through to the play method?
Under what condition will a Puppyplay?
How much does playing increase the puppy’s happiness?
How much does a puppy’s hungriness increase by after playing?
What must be passed through to the eat method?
Under what condition will a Puppyeat?
Is it possible to get a Puppy to eat if his/her hungriness is not greater than 0?
How does the state of the puppy change after eating?
1. All the instance attributes of a Puppy are name, fav_toys, hungriness, happiness.
2. The name argument passed to the constructor, 0, 5 respectively.
3. num_puppies, changes in constructor (when puppies are created), keeps track of total number puppies.
4. All methods have a self parameter.
5. No, bark does not change any attributes.
6. You must pass in string to play which will be assigned to toy.
7. A puppy will only play if his/her hungriness is less than 5.
8. If toy is one of the puppies fav_toys, then happiness will increase by 2. Otherwise, it increases by 1.
9. hungriness increases by 1 after playing regardless of whether the toy is one of the Puppy's favorites.
10. You must pass in string to eat which will be assigned to food.
11. A puppy will only eat if hungriness is not 0.
12. Yes, since eat only checks if self.hungriness is not 0 (remember, 0 is the only integer with a False-y value). If someone sneaky manually sets a puppy's hungriness to a negative number, then the puppy will eat!
13. After eating, a Puppy's hungriness decreases by 1 and happiness increases by 1.
Usage
Now that we have an outline of what Puppy object keeps track of and does, let’s make some puppies!
Instantiating
To create a new puppy object, use the __init__ method. The __init__ method is a magic method, which you’ll learn more about later. For now, just know that you don’t necessarily have to call it using __init__. Instead, just use the class name, like so:
>>>my_puppy=Puppy('Spot',['rope','stick'])
This puppy’s name is 'Spot' and his favorite toys are a 'rope' and a 'stick'. Notice that we do not have to pass anything through for the self parameter. This implicitly gets “passed through”; the new instance that we are creating gets assigned to self!
When __init__ is called, all our instance variables get initialized. Now we can access them and even change them.
Accessing Attributes
Attributes must always be accessed using dot notation.
Instance attributes can only be accessed on an instance. This means that the instance comes before the dot, and the attribute comes after.
>>>my_puppy.name'Spot'
Here, we are using an instance that we’ve already created. We can also create a new instance and retrieve one of its attribute in one line.
Class attributes work the same, except they can also be accessed using the class name. Recall that all instances have access to the class attributes, which have the same values for all instances.
You can change instance or class attributes by using an assignment statement. Changing instance attributes of one instance does not change attributes of other instances.
Here’s the tricky part: attempting to change a class attribute while accessing it from an instance will create a new instance attribute, thereby not affecting the class attribute.
Now, my_puppy no longer has access to the class attribute num_puppies, because it overwrote it with an instance attribute.
Calling Methods
To call a method, you must use dot notation just like with attributes. Otherwise, methods work just like functions; you call them using parentheses and pass through the appropriate arguments.
>>>my_puppy.bark()Woof!MynameisSpotandIloveyou.
Notice that we did not pass through any arguments even though bark takes in one parameter self. Dot notation implicitly passes through the instance and assigns it to self. Thus, in the body, self.name is the same as my_puppy.name.
It is possible to call a method without using dot notation: you can access the method from the class and then pass through the instance as self.
What would Python print after each of the following lines are inputted? Assume we have restarted the interpreter (i.e., ignore all of the above lines).
>>>puppy1=Puppy('Hercules',['squeaky duck'])>>>puppy1.hungriness0>>>Puppy.num_puppies1>>>puppy1.play('stick')>>>puppy2=Puppy('Bruno',['stick','ball'])>>>puppy1.num_puppies2>>>puppy2.play('stick')'stick is my favorite toy EVER.'>>>puppy2.play('ball')'ball is my favorite toy EVER.'>>>puppy1.happiness6>>>puppy2.happiness9>>>for_inrange(4):...puppy1.play('ball')Iamtoohungrytoplay:(>>>puppy1.hungriness5>>>puppy1.eat('canned food')cannedfoodismyfavoritethingtoeatEVER.>>>puppy1.hungriness4>>>puppy2.hungriness2>>>Puppy.num_puppies=17>>>Puppy('Goob',['stuffed rabbit']).num_puppies18>>>puppy1.num_puppies18>>>puppy2.num_puppies=10>>>Puppy.num_puppies18>>>Puppy.__init__(puppy2,'Chewie',['squeaky ball']).bark()Woof!MynameisChewieandIloveyou!