Inheritance is a fundamental concept in Object-Oriented Programming (OOP) that allows a class (called the subclass or derived class) to inherit attributes and methods from another class (called the superclass or base class). It enables code reuse and promotes the "is-a" relationship between classes, allowing you to create hierarchies of related objects with shared characteristics.
When a subclass inherits from a superclass, it gains access to all the attributes and methods of the superclass. This means that the subclass can use these attributes and methods as if they were defined directly within the subclass itself. In Python, inheritance is specified by placing the name of the superclass in parentheses after the name of the subclass during class definition.
Syntax:
class Superclass:
# superclass attributes and methods
class Subclass(Superclass):
# subclass attributes and methods
Inheritance should be used when there is a clear hierarchical relationship between classes and when subclasses share a significant amount of common behavior with the superclass. It is most beneficial for:
Example:
Let's consider an example of a Shape superclass and two subclasses, Rectangle and Circle, which will inherit from Shape. The Shape class defines a generic shape with a color, and both Rectangle and Circle are specific types of shapes with additional attributes and methods.
# Define a parent class 'Shape'
class Shape:
# Constructor method for initializing the color attribute
def __init__(self, color):
self.color = color
# Abstract method for calculating the area of the shape (no implementation)
def area(self):
pass
# Define a child class 'Rectangle' that inherits from the 'Shape' class
class Rectangle(Shape):
# Constructor method for initializing color, length, and width attributes
def __init__(self, color, length, width):
# Call the constructor of the parent class (Shape) to set the color attribute
super().__init__(color)
# Set contructor attributes for height and width
self.length = length
self.width = width
# Method for calculating the area of the rectangle
def area(self):
return self.length * self.width
# Define another child class 'Circle' that also inherits from the 'Shape' class
class Circle(Shape):
# Constructor method for initializing color and radius attributes
def __init__(self, color, radius):
# Call the constructor of the parent class (Shape) to set the color attribute
super().__init__(color)
# Set constructor attribute for radius.
self.radius = radius
# Method for calculating the area of the circle
def area(self):
return 3.14 * self.radius * self.radius
In the code above, we have three classes: Shape, Rectangle, and Circle. The Shape class is the parent class, and Rectangle and Circle are child classes that inherit from Shape. Each class has an area() method that calculates the area of the corresponding shape (rectangle or circle).
rectangle = Rectangle("red", 5, 3)
print(rectangle.color) # Output: "red"
print(rectangle.area()) # Output: 15
circle = Circle("blue", 4)
print(circle.color) # Output: "blue"
print(circle.area()) # Output: 50.24
In this way, inheritance allows us to create a hierarchy of classes that share common functionality while allowing each subclass to specialize and extend that functionality. It promotes code reusability, modularity, and abstraction, making it an essential concept in OOP design.
Inheritance is like a family tree where traits and characteristics are passed from parent classes to child classes. A child class inherits attributes and methods from its parent class and can also have additional unique features.
Polymorphism is a powerful concept in Object-Oriented Programming (OOP) that allows objects of different classes to be treated as objects of a common superclass. It enables a single interface to represent various data types, promoting flexibility and code reuse. In Python, polymorphism is achieved through method overriding and duck typing.
Polymorphism allows different classes to share a common interface or method name, even though they may have different implementations. When a method is called on an object, Python looks for that method in the object's class. If the method is not found in the class, Python searches the class hierarchy until it finds the method.
Method overriding is a form of polymorphism that occurs when a subclass provides a specific implementation for a method that is already defined in its superclass. The subclass overrides the behavior of the superclass method.
class Animal:
def make_sound(self):
return "Some generic sound."
class Dog(Animal):
def make_sound(self):
return "Woof! Woof!"
class Cat(Animal):
def make_sound(self):
return “Meow!”
In this example, the Animal class has a method make_sound(), which provides a generic sound. The Dog and Cat subclasses override this method with their specific sounds. When we call make_sound() on a Dog or Cat object, Python will use the overridden method from the corresponding subclass.
dog = Dog()
cat = Cat()
print(dog.make_sound()) # Output: "Woof! Woof!"
print(cat.make_sound()) # Output: “Meow!”
Python's dynamic typing system allows for a form of polymorphism called "duck typing." This means that if an object behaves like a certain type (quacks like a duck), it can be treated as that type without explicitly specifying its class inheritance.
class Car:
def drive(self):
print("Car is driving.")
class Bike:
def drive(self):
print("Bike is riding.")
def make_vehicle_drive(vehicle):
vehicle.drive()
car = Car()
bike = Bike()
make_vehicle_drive(car) # Output: "Car is driving."
make_vehicle_drive(bike) # Output: “Bike is riding.”
In this example, we have two classes, Car and Bike, each with a drive() method. The function make_vehicle_drive() can take either a Car object or a Bike object and call the drive() method on it without caring about the specific class type.
Polymorphism is beneficial when you have multiple classes with similar behaviors or attributes but specific implementations. It is especially useful when you want to create generic functions or methods that can work with a variety of objects without needing to know their exact types.
Consider a Shape superclass with subclasses Rectangle and Circle. Each subclass has its own implementation of the area() method, demonstrating polymorphism.
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
In this example, the area() method is defined in the Shape superclass but overridden in the Rectangle and Circle subclasses. When calling area() on objects of Rectangle or Circle, Python uses the specific implementation provided by each subclass, resulting in the correct area calculation.
rectangle = Rectangle(5, 3)
circle = Circle(4)
print(rectangle.area()) # Output: 15
print(circle.area()) # Output: 50.24
Polymorphism allows us to treat different shapes (Rectangle and Circle) uniformly as objects of the common superclass (Shape) and call the same method on them, making the code more flexible and reusable.
Polymorphism allows different objects to be treated uniformly based on their common behavior. It provides a consistent interface to different classes, making it easier to work with various objects using the same method calls.
Below is example code for a Vehicle Inventory System with Subclasses for Cars and Trucks, demonstrating Inheritance and Polymorphism:
# Define the parent class 'Vehicle'
class Vehicle:
# Constructor method for initializing common attributes
def __init__(self, make, model, year, last_service_date, odometer):
self.make = make
self.model = model
self.year = year
self.last_service_date = last_service_date
self.odometer = odometer
# Method to get the type of vehicle (to be overridden by subclasses)
def get_vehicle_type(self):
return "Generic Vehicle"
# Define the subclass 'Car' that inherits from 'Vehicle'
class Car(Vehicle):
# Constructor method for initializing car-specific attributes and calling the parent constructor
def __init__(self, make, model, year, last_service_date, odometer, num_doors):
super().__init__(make, model, year, last_service_date, odometer)
self.num_doors = num_doors
# Override the get_vehicle_type() method to return the specific type of vehicle
def get_vehicle_type(self):
return "Car"
# Define the subclass 'Truck' that also inherits from 'Vehicle'
class Truck(Vehicle):
# Constructor method for initializing truck-specific attributes and calling the parent constructor
def __init__(self, make, model, year, last_service_date, odometer, payload):
super().__init__(make, model, year, last_service_date, odometer)
self.payload = payload
# Override the get_vehicle_type() method to return the specific type of vehicle
def get_vehicle_type(self):
return "Truck"
Now, let's demonstrate polymorphism by creating a list of vehicles (cars and trucks) and calling the get_vehicle_type() method for each vehicle. Despite having different subclasses, the method behaves uniformly for all objects in the list.
# Create a list of vehicles (cars and trucks)
vehicles = [
Car("Toyota", "Corolla", 2021, "2023-07-15", 50000, 4),
Truck("Ford", "F-150", 2020, "2023-07-10", 80000, 2000),
Car("Honda", "Civic", 2019, "2023-07-20", 40000, 2),
Truck("Chevrolet", "Silverado", 2018, "2023-07-05", 90000, 3000)
]
# Call the get_vehicle_type() method for each vehicle
for vehicle in vehicles:
print(f"{vehicle.make} {vehicle.model} is a {vehicle.get_vehicle_type()}.")
Toyota Corolla is a Car.
Ford F-150 is a Truck.
Honda Civic is a Car.
Chevrolet Silverado is a Truck.
As shown in the output, the get_vehicle_type() method behaves uniformly for both cars and trucks, demonstrating polymorphism. This allows us to work with objects of different classes using a common interface, making the code more flexible and reusable.
No terms have been published for this module.
Test your knowledge of this module by choosing options below. You can keep trying until you get the right answer.
Skip to the Next QuestionOk - For this last sandbox challenge we will be building on what we did in the last module with our virtual pets. Start by downloading the oop_virtual_pet2.py file and opening it in PyCharm.
Download the oop_virtual_pet2.py File
This file is a bit different. You will see that the clPet class is still there, but in addition to it there are now two more classes: clPetFish and clPetDog.
You might also notice that these “child” classes have a reference to “clPet” in the parenthesis on their class line, which means that they will inherit the properties and methods of the clPet class. They will have a name, hunger and boredom, and will also have the ability to feedPet, playPet and takeTurn. Inheritance is one of the powerful ideas and aspects of OOP. Used properly, it can create some pretty powerful code.
If you look at the __init__ method of these new classes, you will see two things:
First, you will see a line that starts with super( ).__init__(self, strName) — This line triggers the __init__ method of clPet when a clPetFish or clPetDog is created, creating the properties and also passing the strName property to the super (or parent) class.
Second, you will see that in these subclasses, we also create a property called type, which tells us what type of animal it is.
There is one more thing that is interesting about these subclasses:
They have a playPet method defined. Since a playPet method is inherited from clPet, the new playPet overrides the original playPet method, printing out an animal-specific line when we play with them. When we do this in OOP, it is called polymorphism.
Your challenge for this sandbox is to take this code, and add at least one more type of animal. You will need to create a subclass for your new animal type(s), and also edit the menu structure to allow the user to adopt this new animal.