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.