In this video we'll learn about inheritance, multiple inheritance and overriding methods or polymorphism.
Inheritance is when we use the attributes and methods from the parent class and make those attributes and methods available to the child's class.
In our sample code below, we have a Person
class that has the initialization with self
, attribute and attribute2.
You'll notice that in the Enemy
class, we use the Person
class as the parent or base class, and then we have our initialization method, and we include all of the attributes that we would like to have in our Enemy
class.
In order to access all of those attributes, we use the super
method.
In the super
method, all of the attributes of the parent class are accessed. Then we can set any new attributes in the child's class.
We'll build on the Person
class we made in the class video and use the class to learn about inheritance and polymorphism.
Please start this exercise by opening your person.py
file that you used in the classes video. You'll scroll down to the bottom of the file and start by making a new class.
Remember that our class syntax is the word “class” followed by the class name.
We have a class called Enemy
and we want this Enemy
class to inherit from the Person
class.
We want an enemy to be able to do all of the things a person does, so in the parentheses after the class name Enemy
, we'll type the class Person
, since we want the enemy to be able to use the attributes and methods that we made in the Person
class.
Then we'll go ahead and add a colon to the end of our class.
As a reminder, let's scroll up to the person class in recall the attributes and methods from that class.
We had Person.introduce
, Person.emote
, and Person.status_change
as our methods.
We had first name, last name, health and status as attributes.
We'll use all of the attributes in our Enemy
class.
Now, let's scroll back down to our Enemy
class.
Since we have a new class, we'll want to use this class to make Enemy
Objects.
To make these Objects, we'll need an __init__
method or constructor for this Enemy
class. We'll write the first line of the method to contain every attribute that the method will have.
We know that our first name, last name, health and status attributes come from the Person
class, so instead of reassigning those attributes, we'll use the super
method, which will initialize those attributes as they are defined in the parent class.
class Enemy(Person):
def __init__(self, weapon, firstname, lastname, health, status):
super().__init__(firstname, lastname, health, status)
self.weapon = weapon
You'll notice that in our super
method, we've called the first name, last name, health and status attributes.
Because the weapon attribute is a new attribute to the Enemy
Object, we'll need to use the self
argument and self.weapon
to equal “weapon” so that we can access the new attribute in all of our Enemy
class methods.
Now that we've created our Enemy
constructor or __init__
method for our Enemy class
, we can create some new methods for our Enemy
class.
Since we are creating an enemy, our first method will be the Hurt
method.
Well, define our method the same way we've defined all the other methods in our classes before.
In this method, we'll use the conditionals to look at what weapon the enemy has and if the enemy has a certain weapon, then they will deal a certain amount of damage. This is a mechanic that you might recognize if you are a video game player.
We'll start with our if
statement and then elif
statement and then we'll print
our output.
def hurt(self, other):
if self.weapon == 'rock':
other.health -= 10
elif self.weapon == 'stick':
other.health -= 5
print(other.health)
You'll notice that my is if
statement asks the program to compare the weapon that is assigned, and if the weapon of the enemy is a rock, then the person who is the other will have their health reduced by 10.
You'll notice that I'm using the Python Decrement Operator (-=
).
The next method I make will be the insult
method.
You'll notice that my methods in the Enemy
class take 2 arguments: the self
argument and the “other” argument.
def insult(self, other):
if other.health <= 80:
print("{}, you are tired and weak".format(other.firstname))
The self
argument allows access to the enemy and the “other” argument allows access to the other person that I am interacting with.
Finally, we'll create the steal
method.
The steal
method will define an interaction between the enemy and the person.
The steal
method will print a message and if the person was a friend, it will change their friend status to False.
def steal(self, other):
print("ha ha ha, {}, I have your stuff!".format(other.firstname))
if other.status == True:
other.status = False
Now we have 3 methods in our Enemy
class.
Let's unindent our code and create an enemy to interact with some of the people we've created.
Our enemy, Alex Wayne, will have a rock as a weapon and a health status of 75.
Alex = Enemy('rock', 'Alex', 'Wayne', 75, status = False)
Before I try out the class methods, I'd like to just go through and double check my syntax to try and catch any errors.
I'm looking for simple things like any kind of dot notation, single or double equals, colons and spacing. Some IDEs will give you some information.
Overall, this is looking good to me, so now I'd like to try this out.
I'm going to remind you that we have 3 people that we created in our class video. We have Maria, Rey and Lee. So, as a test of the methods, I'm going to have Alex interact with Maria, Rey and Lee.
First, I'll try the hurt
method — Alex.hurt(Maria)
Since Alex has a rock, Maria should lose 10 points of health. Maria's current health is 95, so her health should be 85 if this method acts correctly.
Let's run the code and find out.
Excellent.
The last thing is 85, and that's the outcome I expected.
Next, I'd like to test the insult
method — Alex.insult(Rey)
So, in the insult
method, if the health is less than or equal to 80, then Alex will say, "You are tired and weak."
Rey's health is 88 so that message shouldn't print out, and you'll notice nothing's happened. The message has not printed out.
However, you'll notice that Lee's health was a little bit lower. Let's see if we get a message for Lee.
Finally, we will use our steal
method and test that out — Alex.steal(Rey)
Excellent.
We can see that each of our methods that is inherited from the person class is able to use the different elements of first name, last name, health and status.
For Alex, it's able to give Alex a weapon and then we have specific methods that Alex can use.
It's important to know that the methods that we've created for our enemies are not available to our person class.
In order to test this out, we can try to call an Enemy
method on one of our people.
For example, we can call Rey and use the steal method and put Alex's name.
# error condition
Rey.steal(Alex)
When we run this code, we should expect to see an error, and in fact we do see an error.
There's no message printed out to Alex and more importantly, we have an attribute error, which is that the Person
Object (that's Rey) does not have an attribute steal
.
There's no steal
method for the person class, so this is also acting as we expect.
Multiple inheritance is when one class inherits from multiple classes and is able to use attributes and methods from both classes.
There are pros and cons to using multiple inheritance.
Pros include the ability to reuse small amounts of code in multiple classes and mix-ins.
Cons include the order of inheritance matters. Inheriting from multiple classes can become quite complicated depending on the number of classes, the names of class methods and other factors, including common attributes shared among multiple parent classes.
There can be more maintenance involved when refactoring code that is using multiple inheritance.
There are 2 ways to do multiple inheritance in Python.
The first way is not as Pythonic and requires a bit more maintenance. However, it's easier to see exactly what's happening.
The second way is to continue to use the super
method as we did with a single inheritance. However, this method can be complicated and quite confusing, so we won't use it in the video.
NOTE
You can do research on multiple inheritance in Python and using the super
method and the method resolution order or MRO to learn more about how these things work in Python.
Typically, if we have multiple classes that we want to inherit from, we'll have the attributes of each class and then bring in the attributes of the class individually into the initialization or __init__
method of the child class.
This gives us access to the methods and attributes of both parent classes.
I've made a new file called multiple.py
, and in this file, we're going to take a look at how to use multiple inheritance.
We'll create a simple example so you can see how each of the parent classes is used in the child class.
First, we'll create the parent class. We'll call this class Item
.
In this class, we will create initialization or __init__
method that has the self
and a SKU number.
# Parent class 1
class Item():
def __init__(self, sku):
self.sku = sku
def print_sku(self):
print("The sku is {}.".format(self.sku))
Now we'll create the second parent class. This class will be called the Garment
class.
In the __init__
or constructor method, we will have a section and a type.
# Parent class 2
class Garment():
def __init__(self, section, type):
self.section = section
self.type = type
def print_garment(self):
print("The garment is in section {}, {}.".format(self.section, self.type))
Finally, we'll create our child class, which will inherit from both of the parents.
In our initialization or constructor method, we'll identify all of the attributes from both parent classes, in addition to the attributes that are specific to the child's class.
First, we'll define the attributes that are specific to the child's class.
Now, we'll initialize the attributes from parent class 1, the Item
class.
Next, we'll initialize the attributes from parent class 2, the Garment
class.
# Child Class
class Shirts(Item, Garment):
def __init__(self, sku, section, type, name, color):
self.name = name
self.color = color
Item.__init__(self, sku)
Garment.__init__(self, section, type)
def print_shirt(self):
print("{} {} on sale!".format(self.color, self.name))
So, we have our basic structure of 2 parent classes and 1 child class, and now what I'd like to do is to create 1 method inside of each class to use as an example method.
I'm just going to print a basic message that lets me make sure that I'm getting the attribute that we've defined in the constructor and that I'm able to print that out.
Hopefully this will allow you to see how the child class uses and accesses the attributes and methods from the parent.
Okay, so I have 3 methods:
I have the parent class 1 method of print_sku
The parent class 2 method of print_garment
The child class method of print_shirt
So, first, let's make a child class item.
Blouse = Shirts("00001", 43, "Tops", "Formal Blouse", "White")
And now that I have the child Object, I can experiment with calling each of the parent methods.
We'll start with the print_sku
method, then the print_garment
method from the second parent, and finally, child method.
Blouse.print_sku()
Blouse.print_garment()
Blouse.print_shirt()
So, the child should have access to all 3 of these methods since it inherits from both parent classes.
You can see we have the print_sku
method, the print_garment
method, and the print_shirt
method.
I hope that this simple example helps you to better understand how multiple inheritance works.
Finally, we have polymorphism.
Polymorphism occurs when we want to allow the child class to have a method with the same name and a similar implementation as the parent class and we wish for that method you override the parent class method.
Let's see an example of polymorphism.
We have the introduce method
, which we originally wrote in our Person
class. We also made an Enemy
class and the Enemy
class uses both methods and attributes from the Person
class.
What happens if we want to make a method called introduce
for the Enemy
class?
Let's go ahead and set up that method right out.
Now, instead of the nice introduction that we use for all people, our enemy is going to say something that's a little bit more aggressive.
def introduce(self):
print("You are my mortal enemy!!!")
When we make a method in the child's class with the same name as the one in the parent class, the child class method overrides the parent class method.
Now, the Enemy
Object runs its own code in the introduce
method.
We can run our code and compare the Person.introduce
method to the Enemy.introduce
.
Maria.introduce()
Rey.introduce()
Alex.introduce()
So, we can see that we have, "Hello, my name is Maria.”, and “Hello, my name is Rey.", and those are our person introductions.
But our Enemy
Object introduces by saying, "You are my mortal enemy."
This is the basics of how polymorphism or method overriding work in Python.