Inheritance in Python
CC3 – Object Oriented Programming
Introducing Inheritance
• To recall what this term means, inheritance allows us to create
“is a” relationships between two or more classes.
• This allows us to abstract common logic into super-classes and
manage specific details in the subclass.
• The superclass is the “parent” that has all the methods and
attributes.
• The subclass is the “child” that inherits all the methods and
attributes of the superclass.
Introducing Inheritance
• A new class can be derived from an existing class as follows:
• class MyClass(object):
• pass
• In Python 2.2 and Python 3 onwards, all classes implicitly
inherit from the object class.
• This means we do not need to type object when defining the
parent class.
• This was just shown to demonstrate how Python tells classes
they can be inherited.
Introducing Inheritance
• A new class can be derived from an existing class as follows:
• class SuperClass(object):
• pass
• class SubClass(SuperClass):
• pass
• This syntax tells Python that the “SubClass” should be derived
from the “SuperClass”.
Using Inheritance
• Here is a simple example of how inheritance can be
implemented:
• class Cat:
• def __init__(self, breed, color):
• self.breed = breed
• self.fur_color = color
• def meow(self):
• print("The cat meows.")
Using Inheritance
• Here is a simple example of how inheritance can be
implemented:
• class PersianCat(Cat):
• pass
• persian_cat1 = PersianCat("Persian Cat", "White")
• print(persian_cat1.fur_color) # Outputs: White
• persian_cat1.meow() # Outputs: The cat meows.
Using Inheritance
• As seen in the previous example, the “PersianCat()” class
inherits the attributes and methods of the “Cat()” class.
• This also includes the “__init__” method, which means the child
class will also need to instantiate an object.
• When we would like to use the attributes in the “Cat()” class,
we simply need to call them.
• We do not need to define the same attributes and methods in
our child class “PersianCat()”.
The super() function
• The super function returns the object as an instance of the
parent class, which allows us to call the parent method directly.
• This essentially allows a child class to call all the methods of the
parent class.
• This is meant to allow code reusability.
• This allows you to override methods in your child class, but still
access the “original” methods in your parent class.
Using Inheritance
• We define a super class “Rectangle” with the following
attributes and behaviors:
• class Rectangle:
• def __init__(self, width, height):
• self.width = width
• self.height = height
• def area(self):
• return self.width * self.height
• def perimeter(self):
• return 2 * (self.width * self.height)
Using Inheritance
• We can now define a subclass “Square”.
• Since a square is simply a rectangle with equal sides, these two
shapes share characteristics and can then be inherited.
• Here is the source code example for the subclass “Square”:
• class Square(Rectangle):
• def __init__(self, side):
• super().__init__(side, side)
• self.side = side
Using Inheritance
• We use the “super()” method to allow us to set the sides of the
square with the help of the parent class.
• What is happening is we are asking the user to give a value for
the square when it is instantiated.
• We then pass that value as both the height and width to the
“Rectangle()” class initializer.
• This allows us to set the sides of the square to be equal.
Using Inheritance
• Once you have your superclass and subclass linked together,
you can then start creating objects for both classes.
• Keep in mind that objects created for either class are
independent of each other.
• Here are some examples of objects created along with their
outputs:
• rectangle_example = Rectangle(2, 3)
• print(rectangle_example.area()) # Outputs 6
• print(rectangle_example.perimeter()) # Outputs 12
Using Inheritance
• We can now also create objects for our “Square()” class by
passing only one value:
• square_example = Square(5)
• print(square_example.area()) # Outputs 25
• print(square_example.perimeter()) # Outputs 50
Overriding Class Behavior
Object Oriented Programming
Overriding Class Behavior
• Inheritance allows us not only to add new behavior to existing
classes but change behavior.
• Overriding means altering or replacing a method of the
superclass with a new method in the subclass.
Overriding Class Behavior
• When overriding methods, you are usually only overriding
methods of the same name.
• No special syntax is needed to do this.
• You simply need to recreate the method you would like to
override.
• The subclass’s newly created method is automatically called
instead of the superclass’s method.
Overriding Class Behavior
• Let us say we have a parent class named “Enemy()”:
• class Enemy:
• def __init__(self, hp, mp, attk, deff):
• self.hp = hp
• self.mp = mp
• self.attk = attk
• self.deff = deff
• def attack(self):
• print("The enemy swings their sword at
you!")
Overriding Class Behavior
• We can then have a “Dragon()” class inherit the methods of the
“Enemy()” class:
• class Dragon(Enemy):
• def attack(self):
• print("A dragon spits a massive ball of fire
at you!")
• dragon1 = Dragon(1000, 500, 250, 300)
• dragon1.attack() # A dragon spits a massive ball of
fire at you!
Polymorphism
Object Oriented Programming
Polymorphism
• If you recall, polymorphism is having different behaviors
happen depending on which subclass is being used.
• In this case, we don’t need to know what the subclass is.
• This, when paired with inheritance concepts, allows us to have
a single method or behavior with multiple uses and outputs.
• The simplest way to implement this is to modify the methods in
the child class inherited by the parent class.
Polymorphism
• As an example, let us have a parent class “Dog” with a method
“getDogCoat” and several child classes that inherit this method:
• class Dog:
• def introduceDog(self):
• print("The dog goes bark.")
• def getDogCoat(self):
• pass
• class YorkshireTerrier(Dog):
• def getDogCoat(self):
• print("The Yorkshire Terrier has a Long Dog
Coat.")
Polymorphism
• As an example, let us have a parent class “Dog” with a method
“getDogCoat” and several child classes that inherit this method:
• lass Dobermann(Dog):
• def getDogCoat(self):
• print("The Dobermann has a Short Dog Coat.")
• class Poodle(Dog):
• def getDogCoat(self):
• print("The Poddle has a Curly Coat.")
Polymorphism
• As an example, let us have a parent class “Dog” with a method
“getDogCoat” and several child classes that inherit this method:
• yk = YorkshireTerrier()
• yk.introduceDog() # Outputs: The dog goes bark.
• yk.getDogCoat() # Outputs: The Yorkshire Terrier has
a Long Dog Coat.
• db = Dobermann()
• db.introduceDog() # Outputs: The dog goes bark.
• db.getDogCoat() # Outputs: The Dobermann has a Short
Dog Coat.
Polymorphism
• As an example, let us have a parent class “Dog” with a method
“getDogCoat” and several child classes that inherit this method:
• p = Poodle()
• p.introduceDog() # Outputs: The dog goes bark.
• p.getDogCoat() # Outputs: The Poddle has a Curly
Coat.
Polymorphism
• As seen in the example, the “getDogCoat()” method is used by
multiple child classes of the “Dog” class.
• Despite the child classes all inheriting the “getDogCoat()”, they
overwrite with their own output.
• This allows the “getDogCoat()” to have different outputs
depending on which child classes implements it.
• Polymorphism makes use of concepts in overriding for it to
work.
Abstract Classes
Object Oriented Programming
Abstract Classes
• Abstract base classes (ABC), defines a set of methods and
properties that a class must implement.
• A class can extend an abstract base class itself in order to be
used as an instance of that class.
• In doing this, the class in question must supply all the
appropriate methods.
Creating Abstract Classes
• The first method of implementing abstract classes is quite
simple, we simply give an error if a class is not changed:
• class Fruit:
• def check_ripeness(self):
• raise NotImplementedError("check_ripeness
method not implemented!")
• class Apple(Fruit):
• pass
Creating Abstract Classes
• When we try to implement the class, we will get an error:
• apple1 = Apple()
• apple1.check_ripeness() # Gives a
"NotImplementedError: check_ripeness method not
implemented!"
• This informs the user or developer to implement the specified
class.
• While this encourages implementation of these classes, it does
not enforce their definition.
Creating Abstract Classes
• We can make use of the “abc” module, to enforce
implementation.
• This prevents child classes from being instantiated if they failed
to override abstract class methods:
• from abc import ABCMeta, abstractmethod
• class AbstractClass(metaclass = ABCMeta):
• @abstractmethod
• def required_method(self):
• pass
Creating Abstract Classes
• Now, if we try to have a subclass inherit our main class, it will
not work at all:
• class SubClass(AbstractClass):
• pass
• subclass1 = SubClass()
• subclass1.required_method() # Gives an Error when
Run
NotImplementedError() vs ABC
• You many be wondering why should we go through the trouble
of implementing ABC if both methods give an error.
• In the case of NotImplementedError(), this still allows us to
instantiate an object of an Abstract class without getting an
error.
• We will not get an error until we try to call the abstract
method/s in question.
NotImplementedError() vs ABC
• On the other hand, the ABC module stops us from instantiating
a class completely unless we override the abstract method.
• If we try to instantiate a child class, we will immediately get an
error.
• The error will occur regardless of whether we implement the
abstract methods or not.
Multiple Inheritance
Object Oriented Programming
Multiple Inheritance
• Multiple Inheritance is a case where a subclass that inherits
from multiple parent classes can access functionality from both.
• In practice, this is less useful than it sounds, and it is not
recommended to use this method.
• The simplest and most useful form of multiple inheritance is
called a mixin.
• A mixin is generally a superclass that is not meant to exist on its
own but is meant to be inherited to provide extra functions.
Multiple Inheritance in Diagram
Food
String: name
int: calories
Breakfast_Food beDigested()
Lunch_Food
int: servings
int: protein int: servings
beDigested() beDigested()
Brunch_Food
int: cost
beDigested()
• Breakfast_Food and Lunch_Food
inherit Food
• Brunch_Food inherits both
Breakfast_Food and Lunch_Food
• Output:
name is Food
protein count is 20
serving size is 1
cost is 10
Multiple Inheritance
• Let us say we have a contact list class called “Contacts”:
• class Contact:
• def __init__(self, name, email):
• self.name = name
• self.email = email
• # Other contact information
• def editContactInfo(self):
• # Editing contact information
• def delContactInfo(self):
• # Deleting contact information
Multiple Inheritance
• Next, let’s say we wanted to add some functionality to the
“Contact” class that allows sending an email to “self.email”.
• We can write a mixin class to do the e-mailing:
• class MailSender:
• def send_mail(self, message):
• print("Sending mail to " + self.mail)
• # Email code goes here
• This class doesn’t do anything special, but it allows us to define
a new class that describes both “Contact” and “MailSender”.
Multiple Inheritance
• We can now create a class that makes use of multiple
inheritance:
• class EmailableContact(Contact, MailSender):
• pass
• email = EmailableContact("John Doe",
"[email protected]")
• email.send_mail("Hello, this is a test email!")
Multiple Inheritance
• Just to emphasize that there are better methods to implement
this instead of multiple inheritance, here are some alternatives:
• You can use single inheritance and add the “send_mail” function to the
subclass.
• You can create a standalone Python function for sending an e-mail, and just
call that function with the correct e-email address.