Lecture 7: Inheritance, abstract class
Inheritance, abstract class
Inheritance
Inheritance is a fundamental concept in object-oriented programming (OOP), and it allows a
new class (child) to inherit attributes and methods from an existing class (parent). Let's create an
example with an Animal class as the parent class and a specific type of animal, like a Dog, as the
child class.
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def make_sound(self):
print("Generic animal sound")
def show_info(self):
print(f"Name: {self.name}, Species: {self.species}")
class Dog(Animal):
def __init__(self, name, breed):
# Using super() to call the constructor of the parent class
super().__init__(name, species="Dog")
self.breed = breed
def make_sound(self):
# Accessing the parent class method using super()
super().make_sound()
print("Woof!")
def show_info(self):
# Modifying the parent class method using super()
super().show_info()
print(f"Breed: {self.breed}")
# Creating instances of the classes
generic_animal = Animal(name="Generic Animal", species="Unknown")
my_dog = Dog(name="Buddy", breed="Golden Retriever")
# Using methods of the parent class
generic_animal.show_info()
# Using methods of the child class
my_dog.show_info()
# Overriding the parent class method in the child class
my_dog.make_sound()
In this example:
• The Animal class has an __init__ method to initialize the name and species
attributes, a make_sound method, and a show_info method to display information
about the animal.
• The Dog class inherits from the Animal class using class Dog(Animal):. It has its
own __init__ method to initialize the breed attribute and overrides both the
make_sound and show_info methods from the parent class using super().
• When creating an instance of Dog, we use super().__init__(name,
species="Dog") to call the constructor of the parent class and initialize the name
and species attributes.
• The make_sound method in the Dog class first calls the make_sound method of the
parent class using super().make_sound() and then adds the specific sound for a
dog.
• The show_info method in the Dog class similarly calls the show_info method of
the parent class using super().show_info() and then adds information about the
dog's breed.
When you run this code, you should see output demonstrating how the child class (Dog) inherits
and modifies the behavior of the parent class (Animal).
Multi-level Inheritance in python
In Python, there is no strict limit on the number of levels of inheritance you can have. You can
create as many levels of inheritance as needed for your program. However, it's essential to keep
the design simple and maintainable.
Let's demonstrate a simple example with multiple levels of inheritance:
# Grandparent class
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclasses must implement this
method")
# Parent class inheriting from Animal
class Mammal(Animal):
def give_birth(self):
return f"{self.name} is giving birth to live young."
# Child class inheriting from Mammal
class Dog(Mammal):
def speak(self):
return f"{self.name} says Woof!"
# Grandchild class inheriting from Dog
class Poodle(Dog):
def fetch(self):
return f"{self.name} is fetching the ball."
# Creating instances of the classes
my_dog = Poodle(name="Buddy")
# Accessing methods from all levels of inheritance
print(my_dog.speak()) # Output: Buddy says Woof!
print(my_dog.give_birth()) # Output: Buddy is giving birth to live
young.
print(my_dog.fetch()) # Output: Buddy is fetching the ball.
In this example:
• Animal is the grandparent class, which provides a basic structure with the __init__
and speak methods.
• Mammal is the parent class inheriting from Animal and adding a give_birth method.
• Dog is the child class inheriting from Mammal and providing its own implementation of
the speak method.
• Poodle is the grandchild class inheriting from Dog and adding a fetch method.
You can continue this pattern to create more levels of inheritance. However, it's crucial to strike
a balance and avoid excessive levels, as it can make the code complex and harder to understand.
In practice, it's often recommended to keep the number of inheritance levels to a minimum and
favor composition over deep inheritance hierarchies when possible.
Multiple Inheritance
What is Multiple Inheritance in Python?
Multiple inheritance is when a class inherits from more than one parent class. This allows a child
class to use methods and attributes from multiple parents.
Example:
class Parent1:
def greet(self):
print("Hello from Parent1!")
class Parent2:
def greet(self):
print("Hello from Parent2!")
class Child(Parent1, Parent2):
pass
c = Child()
c.greet() # Which parent's greet() will run?
Key Problem: The Diamond Problem
When two parent classes have methods with the same name, Python might get confused about
which method to use. This is called the Diamond Problem.
Example:
class A:
def say(self):
print("Hello from A")
class B(A):
def say(self):
print("Hello from B")
class C(A):
def say(self):
print("Hello from C")
class D(B, C):
pass
d = D()
d.say() # Which 'say' method will run?
In this case, Python resolves the confusion using MRO (Method Resolution Order).
Method Resolution Order (MRO)
• Python uses the C3 Linearization Algorithm to decide the order of method resolution.
• You can check the MRO using ClassName.__mro__.
Check MRO:
print(D.__mro__)
Why Avoid Multiple Inheritance?
• Confusion: Method conflicts can make debugging hard.
• Complexity: Harder to understand the flow of the program.
Simple Fix for Method Conflicts:
Call specific methods using the class name:
class D(B, C):
def say(self):
B.say(self)
C.say(self)
d = D()
d.say()
Takeaway:
While multiple inheritance can be powerful, it’s better to use it carefully to avoid confusion and
conflicts.
#Abstract classs vs Interface
• Abstract Class: Can have both abstract and concrete methods, allows state (member
variables), supports single inheritance, and is used to share common functionality among
related classes.
• Interface: Defines only abstract methods, usually doesn’t have state, supports multiple
inheritance (can implement multiple interfaces), and is used to enforce a contract of
behavior across unrelated classes.
The main difference: Abstract classes provide some implementation, while interfaces just
define what methods a class must implement.
ABC and Interface
In Python, abstract classes and interfaces are implemented using the abc (Abstract Base Class)
module, which allows you to define abstract methods that must be implemented by subclasses.
However, Python doesn't have a formal "interface" concept like Java or C#, but abstract classes
can be used to achieve similar functionality.
Abstract Class
An abstract class in Python serves as a blueprint for other classes. It can have both concrete
methods (methods with implementation) and abstract methods (methods that must be
implemented by subclasses). Abstract classes cannot be instantiated.
How to define an abstract class:
• You need to import ABC and abstractmethod from the abc module.
• Any class inheriting from ABC can have one or more abstract methods that must be
implemented by subclasses.
Example of Abstract Class:
from abc import ABC, abstractmethod
# Define an abstract class
class Animal(ABC):
@abstractmethod
def sound(self):
pass # Abstract method, must be implemented in subclasses
def sleep(self):
print("This animal is sleeping.") # Concrete method, with
implementation
# Define a subclass
class Dog(Animal):
def sound(self):
print("Woof woof!") # Implements the abstract method
class Cat(Animal):
def sound(self):
print("Meow!") # Implements the abstract method
# Usage
dog = Dog()
dog.sound() # Woof woof!
dog.sleep() # This animal is sleeping.
cat = Cat()
cat.sound() # Meow!
cat.sleep() # This animal is sleeping.
In this example:
• The Animal class is abstract with an abstract method sound().
• The Dog and Cat classes inherit from Animal and must implement the sound()
method.
• The sleep() method is concrete and can be used directly by subclasses.
Interface (Simulated through Abstract Class)
In Python, interfaces can be mimicked using abstract classes with only abstract methods. In this
way, the class acts as an interface, and the implementing classes must provide their own
implementations.
Example of Interface-like Behavior:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
# Implementing the interface
class Rectangle(Shape):
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)
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
def perimeter(self):
return 2 * 3.14 * self.radius
# Usage
rect = Rectangle(4, 5)
print("Rectangle Area:", rect.area()) # Rectangle Area: 20
print("Rectangle Perimeter:", rect.perimeter()) # Rectangle
Perimeter: 18
circle = Circle(3)
print("Circle Area:", circle.area()) # Circle Area: 28.26
print("Circle Perimeter:", circle.perimeter()) # Circle Perimeter:
18.84
In this example:
• Shape acts like an interface with abstract methods area() and perimeter().
• Rectangle and Circle implement the Shape interface by providing concrete
definitions of area() and perimeter().
Key Points:
• Abstract classes can have both abstract and concrete methods, while an "interface-like"
abstract class contains only abstract methods.
• Abstract classes cannot be instantiated, and subclasses must implement the abstract
methods.
• The abc module is used to create abstract classes and methods in Python.
Would you like practice problems related to abstract classes or interfaces?