OOP with Python - Part 2
OOP with Python - Part 2
We covered the basics of Object-Oriented Programming (OOP), including Classes and Objects, Encapsulation, and Inheritance in OOP with Python - Part 1. In this second part, we will discuss the remaining two key principles of OOP; Abstraction and Polymorphism.
Abstraction
Abstraction is the concept of hiding the complex implementation details of a system and exposing essential features of an object. This allows us to focus on what an object does but not how it is done. This can reduce programming complexity and enhance code maintainability.
In Python, abstraction can be achieved using abstract classes and methods provided by the abc module. An abstract class is a class that cannot be instantiated and typically contains one or more abstract methods. An abstract method is a method that is declared but contains no implementation. Subclasses of the abstract class must provide implementations for all abstract methods. Hence, abstract class acts as a blueprint for subclasses.
Let’s extend our Shape example from previous article to include abstraction. In real life, since Shape is more of an umbrella term which encompasses different types of shapes, we may implement it as an abstract class. Then, we can implement concrete subclasses like Square or Rectangle to represent different types of shapes.
from abc import ABC, abstractmethod
class Shape(ABC): # Abstract class Shape
shape_type = "Quadrilateral" # Class attribute
def __init__(self, name): # Constructor
self.shape_name = name # Public object attribute
@abstractmethod
def set_area(self): # Abstract setter method
pass
@abstractmethod
def get_area(self): # Abstract getter method
pass
Here, we have defined an abstract class Shape with two abstract methods: set_area and get_area. Inside the methods, we may use the pass statement which acts as a placeholder code snippet in Python. This class cannot be instantiated directly. Instead, we will create concrete subclasses that provide implementations for the abstract methods.
Let’s implement the Square subclass.
class Square(Shape):
"""
Subclass Square
Extends abstract class Shape
"""
def __init__(self, name, side): # Constructor
super().__init__(name)
self.side = side # Public object attribute
self.__area = 0 # Private object attribute
def set_area(self): # Concrete setter method
self.__area = self.side**2
def get_area(self): # Concrete getter method
return self.__area
In the subclass, we have provided concrete implementations for the abstract methods. Now, we can create objects of the Squareclass.
square = Square("Square", 8)
square.set_area()
print(f"Shape Name: {square.shape_name}, Shape Type: {Shape.shape_type}")
print(f"Area: {square.get_area()}")
The output of this code will be:
Shape Name: Square, Shape Type: Quadrilateral
Area: 64
Polymorphism
In OOP, polymorphism is the concept of treating objects of different subclasses as objects of a common superclass. Polymorphism is achieved through method overriding and method overloading. However, Python directly doesn’t support method overloading (compile-time polymorphism), hence, we will discuss method overriding in this chapter.
Method overloading is having multiple methods in the same class with same name but different parameters (type or value or both). In Python, we can achieve similar functionality by using default arguments or variable-length arguments.
Method overriding (runtime polymorphism) occurs when a subclass provides a specific implementation of a method that is already defined in its superclass. The overridden method in the subclass must have the same name and the parameter list as the method in the superclass.
To illustrate polymorphism, we will implement another sub class, Rectangle. In the following example we have implemented the set_area method to calculate the area of a rectangle. This implementation replaces the pass statement of the set_area method of the super class Shape.
class Rectangle(Shape):
"""
Subclass Rectangle
Extends abstract class Shape
"""
def __init__(self, name, length, width): # Constructor
super().__init__(name)
self.length = length # Public object attribute
self.width = width # Public object attribute
self.__area = 0 # Private object attribute
def set_area(self): # Concrete setter method
self.__area = self.length * self.width
def get_area(self): # Concrete getter method
return self.__area
Now, we can create an object of the Rectangle subclass.
rectangle = Rectangle("Rectangle", 10, 8)
rectangle.set_area()
print(f"Shape Name: {rectangle.shape_name}, Shape Type: {Shape.shape_type}")
print(f"Area: {rectangle.get_area()}")
The output of this code will be:
Shape Name: Rectangle, Shape Type: Quadrilateral
Area: 80
Complete OOP Code Example
Let’s put everything together and write a complete example to cover all the four concepts we discussed.
from abc import ABC, abstractmethod
class Shape(ABC): # Abstract class Shape
shape_type = "Quadrilateral" # Class attribute
def __init__(self, name): # Constructor
self.shape_name = name # Public object attribute
@abstractmethod
def set_area(self): # Abstract setter method
pass
@abstractmethod
def get_area(self): # Abstract getter method
pass
class Square(Shape):
"""
Subclass Square
Extends abstract class Shape
"""
def __init__(self, name, side): # Constructor
super().__init__(name)
self.side = side # Public object attribute
self.__area = 0 # Private object attribute
def set_area(self): # Concrete setter method
self.__area = self.side**2
def get_area(self): # Concrete getter method
return self.__area
class Rectangle(Shape):
"""
Subclass Rectangle
Extends abstract class Shape
"""
def __init__(self, name, length, width): # Constructor
super().__init__(name)
self.length = length # Public object attribute
self.width = width # Public object attribute
self.__area = 0 # Private object attribute
def set_area(self): # Concrete setter method
self.__area = self.length * self.width
def get_area(self): # Concrete getter method
return self.__area
square = Square("Square", 8)
square.set_area()
rectangle = Rectangle("Rectangle", 10, 8)
rectangle.set_area()
print(f"Shape Name: {square.shape_name}, Shape Type: {Shape.shape_type}")
print(f"Area: {square.get_area()}")
print(f"Shape Name: {rectangle.shape_name}, Shape Type: {Shape.shape_type}")
print(f"Area: {rectangle.get_area()}")
Output:
Shape Name: Square, Shape Type: Quadrilateral
Area: 64
Shape Name: Rectangle, Shape Type: Quadrilateral
Area: 80
Our code has implemented encapsulation through separate classes with setters and getters, ensuring data integrity. Each class contains data and methods related to a specific shape. To implement inheritance we have extended Square and Rectangle classes to the Shape class, providing a structure for code reuse. Abstraction is implemented by defining the Shape class as an abstract class, reducing complexity. Polymorphism is shown by overriding the set_area method in Square and Rectangle classes, enabling flexibility.
Comments
Post a Comment