An
Introduction to Object Oriented Programming, Classes and Inheritance (in
Python)
What is Object
Oriented Programming?
Object Oriented
Programming (OOP) is based on the idea of classes and objects. It organizes a computer
program into basic, reusable blueprints of code or “classes.” These classes are
then used and reused to create new and unique objects with similar functions.
What is a Class?
Data structures like lists and strings
are extremely useful, but sometimes they aren’t enough to represent something
you’re trying to implement. For example, let’s say we needed to keep track of a
bunch of pets. We could represent a pet using a list by specifying the first
element of the list as the pet’s name and the second element of the list as the
pet’s species. This is very arbitrary and nonintuitive,
however – how do you know which element is supposed to be which?
Classes give us the ability to create
more complicated data structures that contain arbitrary content. We can create
a Pet class that
keeps track of the name and species of the pet in usefully named attributes
called name and species, respectively.
What is an Instance?
Before we get into creating a class
itself, we need to understand an important distinction. A class is something
that just contains structure – it defines how something should be laid out or
structured, but doesn’t actually fill in the content. For example, a Pet class may say that a pet needs to
have a name and a species, but it will not actually say what the pet’s name or
species is. This is where instances come in. An instance is a
specific copy of the class that does contain all of the content. For example,
if I create a pet polly, with name "Polly" and
species "Parrot", then polly is an instance
of Pet. This can sometimes
be a very difficult concept to master, so let’s look at it from another angle.
Let’s say that the government has a particular tax form that it requires
everybody to fill out. Everybody has to fill out the same type of
form, but the content that people put into the form differs from person to
person. A class is like the form: it specifies what content
should exist. Your copy of the form with your specific information if like
an instance of the class: it specifies what the content
actually is.
Keeping Track of Pets
Now that we have an idea of what a
class is and what the difference between a class and an instance is, let’s look
at a real class!
Pet
Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Pet(object): def __init__(self, name, species): self.name = name self.species
= species def getName(self): return self.name def getSpecies(self): return self.species def __str__(self): return "%s is a %s" %
(self.name, self.species) |
Line 1
This is the basic incant for creating a
class. The first word, class,
indicates that we are creating a class. The second word, Pet, indicates the name of the class. The
word in parentheses, object,
is the class that Pet is inheriting from.
We’ll get more into inheritance below, so for now all you need to know is
that object is a special
variable in Python that you should include in the parentheses when you are
creating a new class.
Lines 3-5
When we create a new pet, we need to
initialize (that is, specify) it with a name and a species. The __init__ method (method is
just a special term for functions that are part of a class) is a special Python
function that is called when an instance of a class is first created. For
example, when running the code polly = Pet("Polly",
"Parrot"),
the __init__ method is called with values polly, "Polly", and "Parrot" for the
variables self, name, and species, respectively.
The self variable is the instance of
the class. Remember that instances have the structure of the class but that the
values within an instance may vary from instance to instance. So, we want to
specify that our instance (self)
has different values in it than some other possible instance. That is why we
say self.name
= name instead
of Pet.name
= name.
You might have noticed that the __init__ method (as well
as other methods in the Pet class)
have this self variable, but that when you
call the method (e.g. polly = Pet("Polly",
"Parrot")),
you only have to pass in two values. Why don’t we have to pass in the self parameter? This phenomenon is
a special behavior of Python: when you call a method of an instance, Python
automatically figures out what self should be (from the instance) and
passes it to the function. In the case of __init__, Python first
creates self and then passes
it in. We’ll talk a little bit more about this below when we discuss the getName and getSpecies methods.
Lines 7-11
We can also define methods to get the
contents of the instance. The getName method takes an
instance of a Pet as a parameter
and looks up the pet’s name. Similarly, the getSpecies method takes an
instance of a Pet as a parameter
and looks up the pet’s species. Again, we require the self parameter so that the function
knows which instance of Pet to
operate on: it needs to be able to find out the content.
As mentioned before, we don’t actually
have to pass in the self parameter because Python
automatically figures it out. To make it a little bit clearer as to what is
going on, we can look at two different ways of calling getName. The first way is
the standard way of doing it: polly.getName(). The second, while not conventional,
is equivalent: Pet.getName(polly). Note how in the second example we had
to pass in the instance because we did not call the method via the
instance. Python can’t figure out what the instance is if it doesn’t have
any information about it.
To drive the difference between
instances and classes home, we can look at an explicit example:
1 2 3 4 5 6 7 8 9 10 11 |
>>> from pets
import Pet >>> polly = Pet("Polly", "Parrot") >>> print
"Polly is a %s" % polly.getSpecies() Polly is a Parrot >>> print
"Polly is a %s" % Pet.getSpecies(polly) Polly is a Parrot >>> print
"Polly is a %s" % Pet.getSpecies() Traceback (most recent call
last): File "", line 1, in TypeError: unbound method getSpecies() must be called with Pet instance as first argument (got
nothing instead) |
Lines 13-14
This __str__ method is a
special function that is defined for all classes in Python (you have have noticed that methods beginning and ending with a
double underscore are special). You can specify your own version of any
built-in method, known as overriding the method. By overriding
the __str__ method
specifically, we can define the behavior when we try to print an instance of
the Pet class using
the print keyword.
Using Classes
Let’s look at some examples of using
the Pet class!
Polly the Parrot
1 2 3 4 5 6 7 8 |
>>> from
pets import Pet >>> polly = Pet("Polly", "Parrot") >>> polly.getName() 'Polly' >>> polly.getSpecies() 'Parrot' >>> print polly Polly is a Parrot |
Ginger the Cat
1 2 3 4 5 6 7 8 |
>>> from
pets import Pet >>> ginger
= Pet("Ginger", "Cat") >>> ginger.getName() 'Ginger' >>> ginger.getSpecies() 'Cat' >>> print
ginger Ginger is a Cat |
Clifford the Dog
1 2 3 4 5 6 7 8 |
>>> from
pets import Pet >>> clifford = Pet("Clifford", "Dog") >>> clifford.getName() 'Clifford' >>> clifford.getSpecies() 'Dog' >>> print clifford Clifford is a Dog |
Subclasses
Sometimes just defining a single class (like Pet) is not enough. For example, some pets
are dogs and most dogs like to chase cats, and maybe we want to keep track of
which dogs do or do not like to chase cats. Birds are also pets but they
generally don’t like to chase cats. We can make another class that is a Pet but is also specifically a Dog, for example: this gives us the
structure from Pet but also any
structure we want to specify for Dog.
Dog Class
1 2 3 4 5 6 7 8 |
class Dog(Pet): def __init__(self, name, chases_cats): Pet.__init__(self,
name, "Dog") self.chases_cats
= chases_cats def chasesCats(self): return self.chases_cats |
We want to specify that all Dogs have species "Dog", and also whether or
not the dog likes to chase cats. To do this, we need to define our own
initialization function (recall that this is known as overriding).
We also need to call the parent class initialization function, though, because
we still want the name and species fields to be initialized. If we
did not have line 4, then we could still call the methods getName and getSpecies. However,
because Pet.__init__ was never called, the name and species fields were never created, so
calling getName or getSpecies would throw an
error.
We can define a similar subclass for
cats:
Cat Class
1 2 3 4 5 6 7 8 |
class Cat(Pet): def __init__(self, name, hates_dogs): Pet.__init__(self,
name, "Cat") self.hates_dogs
= hates_dogs def hatesDogs(self): return self.hates_dogs |
A Closer Look at
Inheritance
Let’s examine the difference
between Dog and Pet.
1 2 3 |
>>> from
pets import Pet, Dog >>> mister_pet = Pet("Mister", "Dog") >>> mister_dog = Dog("Mister", True) |
The function used below, isinstance, is a special
function that checs to see if an instance is an
instance of a certain type of class. Here we can see that mister_pet is an instance
of Pet, but not Dog, while mister_dog is an instance
of both Pet and Dog:
1 2 3 4 5 6 7 8 |
>>> isinstance(mister_pet, Pet) True >>> isinstance(mister_pet, Dog) False >>> isinstance(mister_dog, Pet) True >>> isinstance(mister_dog, Dog) True |
Because mister_pet is a Pet, but not a Dog, we can’t call chasesCats on it because
the Pet class has
no chasesCats method. We can, however, call chasesCats on mister_dog, because it is
defined for the Dog class.
Conversely, we can call the getName method on
both mister_pet and mister_dog because they
are both instances of Pet,
even though getName is not explicitly defined in
the Dog class.
1 2 3 4 5 6 7 8 9 10 |
>>> mister_pet.chasesCats() Traceback (most recent call
last): File "", line 1, in AttributeError: 'Pet' object has
no attribute 'chasesCats' >>> mister_dog.chasesCats() True >>> mister_pet.getName() 'Mister' >>> mister_dog.getName() 'Mister' |
Cats and Dogs
Now let’s create some cats and dogs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
>>> from
pets import Cat, Dog >>> fido = Dog("Fido", True) >>> rover
= Dog("Rover", False) >>>
mittens = Cat("Mittens", True) >>> fluffy
= Cat("Fluffy", False) >>> print fido Fido is a Dog >>> print
rover Rover is a Dog >>> print
mittens Mittens is a Cat >>> print
fluffy Fluffy is a Cat >>> print
"%s chases cats: %s" % (fido.getName(), fido.chasesCats()) Fido chases cats:
True >>> print
"%s chases cats: %s" % (rover.getName(), rover.chasesCats()) Rover chases cats:
False >>> print
"%s hates dogs: %s" % (mittens.getName(),
mittens.hatesDogs()) Mittens hates dogs:
True >>> print
"%s hates dogs: %s" % (fluffy.getName(), fluffy.hatesDogs()) Fluffy hates dogs:
False |
Hopefully this was helpful in
explaining what classes are, why we would want to use them, and how to subclass
classes to create multiple layers of structure.
======================================================
– Instructions – ==========================================================
# pets.py class Pet(object): def __init__(self, name, species): self.name = name self.species
= species def getName(self): return self.name def getSpecies(self): return self.species def __str__(self): return "%s is a %s" %
(self.name, self.species) class Dog(Pet): def __init__(self, name, chases_cats): Pet.__init__(self,
name, "Dog") self.chases_cats
= chases_cats def chasesCats(self): return self.chases_cats class Cat(Pet): def __init__(self, name, hates_dogs): Pet.__init__(self,
name, "Cat") self.hates_dogs
= hates_dogs def hatesDogs(self): return self.hates_dogs |
# Pet_Demo.py from pets import
Cat, Dog fido =
Dog("Fido", True) rover =
Dog("Rover", False) mittens =
Cat("Mittens", True) fluffy =
Cat("Fluffy", False) print(fido) print(rover) print(mittens) print(fluffy) print("%s
chases cats: %s" % (fido.getName(), fido.chasesCats())) print("%s
chases cats: %s" % (rover.getName(), rover.chasesCats())) print("%s
hates dogs: %s" % (mittens.getName(), mittens.hatesDogs())) print("%s
hates dogs: %s" % (fluffy.getName(), fluffy.hatesDogs())) |
1.
Make a folder on your computer called
"Class"
2.
Very carefully type in both
programs given above (pets.py and Pet_Demo.py)
and save them both into the "Class" folder you created in Step 1
above
(one of the most common errors students make is to accidently not capitalized the
P in Pet following the word class in the pets.py
program)
3.
Close the pets.py program and run the Pet_Demo.py
4.
The output should exactly equal
the output given below in Figure 1
5.
Once your output is correct,
submit ONLY the Pet_Demo.py to
Canvas.
Figure
1
= RESTART:
C:\Program Files\Python311\pet_demo.py Fido is a Dog Rover is a Dog Mittens is a Cat Fluffy is a Cat Fido chases cats:
True Rover chases cats:
False Mittens hates dogs:
True Fluffy hates dogs:
False |
The End
It may be helpful
to review the document before the instructions break, to make sure you
understand all the concepts.