Last week we learned the basic structure of python classes and introduced new semantics: classes, instances, methods, attributes... Today we have a short unit about a very important feature of most OOP languages: inheritance.
Inheritance is a core concept of OOP. It allows a subclass (also called "child class") to override or extend methods and attributes from a base class (also called "parent class"). In other words, child classes inherit all of the parent's attributes and behaviors but can also specify new behaviors or replace old ones.
This is best shown with an example: let's make the Cat
and Dog
class inherit from the Pet
class.
class Pet:
# Initializer
def __init__(self, name, weight):
self.name = name
self.weight = weight
def eat_food(self, food):
self.weight += food
@property
def weight_lbs(self):
return self.weight / 0.45359237
def say_name_loudly(self):
return self.say_name().upper()
class Cat(Pet):
# Class attribute
language = 'Meow'
# Method
def say_name(self):
return '{}, my name is {} and I am nice!'.format(self.language, self.name)
class Dog(Pet):
# Class attribute
language = 'Woof'
# Method
def say_name(self):
return '{}, my name is {} and I smell bad!'.format(self.language, self.name)
Let's advance through this example step by step.
First, let's have a look at the Pet
class. It is a standard class defined the exact same way as in the previous lecture. Therefore, it can be instantiated and will work as expected:
p = Pet('PetName', 10)
p.weight_lbs
The functionality of the class Pet
however is very general, and it is unlikely to be used alone (a pet
isn't specific enough to most people: is it a cat, a fish, a dog?). We used this class to implement the general functionality supported by all pets: they have a name and a weight, regardless of their species.
Now comes the important part: the Cat
and Dog
classes make use of these functionalities by inheriting from the Pet
parent class. This inheritance is formalized in the class definition class Cat(Pet)
. The code of the two child classes is remarkably simple: it adds only a new functionality to the ones already inherited from Pet
. For example:
c = Cat('Kitty', 4)
c.say_name()
The Pet
instance methods are still available:
c.eat_food(0.2)
c.weight
d = Dog('Charlie', 8)
d.say_name()
There is a pretty straightforward rule for the behavior of child classes instances: when the called method or attribute is available at the child class level, it will be used (even if also available at the parent class level: this is called overriding, and will be explained next week); if not, use the parent class implementation.
This is exactly what happens in the code above: eat_food
and weight
are defined in the Pet
class but are available for both Cat
and Dog
instances. say_name
, however, is a child class instance method and can't be used by Pet
instances.
This relationship between parent and child classes can be formalized as following:
print('Is d a Dog?', isinstance(d, Dog))
print('Is d also a Pet?', isinstance(d, Pet))
print('Is d also a Cat?', isinstance(d, Cat))
However, a Pet is neither a Cat or a Dog:
print('Is p a Dog?', isinstance(p, Dog))
print('Or a Cat?', isinstance(p, Cat))
So, what about the say_name_loudly
method? Although available for Pet
instances, calling it will raise an error:
p = Pet('PetName', 10)
p.say_name_loudly() # this raises an AttributeError
What happened here? It's correct, the class "Pet" has no say_name
method!
In fact, it was intended behavior: since say_name_loudly
is available to the child class instances, the method will work for them! See for instance:
d = Dog('Charlie', 8)
d.say_name_loudly()
This is a typical use case for class inheritance in OOP: it allows code re-use. We will talk about more advanced use cases next week.
This was only a (very) brief introduction to the concept of inheritance. Next week we will discuss concrete (and more advanced) use cases for inheritance and OOP in general.
Back to the table of contents, this week's assignment or jump to the pelita game.