OOP with Python - Part 1

OOP with Python - Part 1

OOP with Python - Part 1

OOP (Object Oriented Programming) is a programming paradigm (a design style or an approach with its related design principles and techniques) which considers objects as the primary building block of a software program. In our previous articles, we were following a procedural approach where we were writing our code in a sequential manner. As compared to the procedural approach, OOP provides benefits like modularity, ease of maintenance, flexibility, scalability and reusability which makes it ideal for large scale real world projects.

Python, being a general purpose language, supports both procedural and OOP paradigms. So, in this article let’s talk about the basics of OOP and how to implement them in Python.

Class & Object

An object is a real world entity with properties (aka attributes or data or members) and behaviors (aka methods or functions). For instance, a cat is an object. It has a name, color and a breed (attributes) and it meows (behavior). A class is an abstract idea which describes that real life entity. For instance, the cat we talked about before is an object of the class Animal. A class can define attributes to describe properties of an object and methods to describe the behaviors. Accordingly, a class is an abstract template which is used to create actualized instances.

Let’s look at the syntax for python classes and objects in the below example. We have implemented a class named Shape. On the top of the class we have defined a class attribute named shape_type. Class attributes are common for all the objects created by the class. Hence, our class attribute implies that we will be only creating shapes of quadrilaterals type.

The method inside the class (__init__) is a special method named constructor. A constructor is used to create (construct) objects of a class. The first parameter inside the constructor (self) is a conventional word in Python that refers to the current object and it is used inside the constructor to define object attributes. The second parameter (name) is used to pass a value for the object attribute (shape_name). The line self.shape_name = name is used to assign the value (name) of the parameter to the attribute (shape_name).

class Shape:
    shape_type = "Quadrilateral"     # Class attribute

    def __init__(self, name):
        # Constructor
        self.shape_name = name  # Object attribute


square = Shape("Square")
print(f"Shape Name: {square.shape_name}, Shape Type: {Shape.shape_type}")

Then we have created an object with the line square= Shape("Square"). Here, the square refers to the object name. Shape() method invokes the constructor of the class Shape and we can pass a value ("Square") for the shape_name attribute in method arguments. Now, we can access the attributes and methods of the class using the object name and the dot operator.
Note: we have to use the object name to access the object attribute {square.shape_name} and class name to access the class attribute {Shape.shape_type}
So, our code outputs the following:

Shape Name: Square, Shape Type: Quadrilateral

There are 04 main concepts in OOP, namely, Encapsulation, Inheritance, Abstraction and Polymorphism.

Encapsulation

Encapsulation is the concept of bundling attributes and methods inside a single unit (class). This enhances reusability, maintainability and security of the code.

To introduce encapsulation to our previous example, we need to know about three more concepts, private attributes, getters and setters. In Python, a private attribute is defined with two leading underscore characters before the variable name (__area). The scope of a private attribute is limited to the class and cannot be accessed outside of the class. Hence, we have to use two special methods called set method (setter) and get method (getter). Setter can be used to calculate and assign values (to manipulate data within the class) to the private attribute while getter can be used to return the value (to access data from outside the class) of the private attribute.

class Shape:
    shape_type = "Quadrilateral"  # Class attribute

    def __init__(self, name, length):
        # Constructor
        self.shape_name = name  # Public object attribute
        self.shape_length = length
        self.__area = 0  # Private object attribute

    def set_area(self):
        """
        Setter method
        Calculate the value of area
        """
        self.__area = self.shape_length**2

    def get_area(self):
        """
        Getter method
        Return the value of area
        """
        return self.__area


square = Shape("Square", 800)
square.set_area()

print(f"Shape Name: {square.shape_name}, Shape Type: {Shape.shape_type}")
print(f"Area: {square.get_area()}")

Accordingly, we have modified the code snippet to add a private attribute (self.__area), a setter (set_area) and a getter (get_area). And we access the getter method by using the object name and the dot operator {square.get_area()}.
So, our code outputs the following:

Shape Name: Square, Shape Type: Quadrilateral
Area: 64

Inheritance

Inheritance is when one class (sub class or child class) is inheriting attributes and methods of another class (super class or parent class). This enhances code reusability and readability by providing a logical structure to the program.

Let’s modify our previous program to introduce inheritance.

class Shape:
    """
    Super class
    Inherited by Square and Rectangle sub classes
    """

    shape_type = "Quadrilateral"  # Class attribute

    def __init__(self, name, length, width):
        # Constructor
        self.shape_name = name  # Public object attribute
        self.shape_length = length
        self.shape_width = width
        self.__area = 0  # Private object attribute

    def set_area(self):
        """
        Setter method
        Calculate the value of area
        """
        self.__area = self.shape_length * self.shape_width

    def get_area(self):
        """
        Getter method
        Return the value of area
        """
        return self.__area


class Square(Shape):
    """
    Sub class - extends Shape class
    """

    def __init__(self, name, side):
        # Super class constructor invoke
        super().__init__(name, side, side)


class Rectangle(Shape):
    """
    Sub class - extends Shape class
    """

    def __init__(self, name, length, width):
        # Super class constructor invoke
        super().__init__(name, length, width)


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()}")

Accordingly, we have created two additional classes as sub classes ( class Square(Shape) and class Rectangle(Shape) ) and they extend the super class (class Shape). Inside the sub class constructor, we have invoked the super class constructor by using the super() function. Then, we can create objects using the sub class constructors and we can access the super class getter method by using the object name.
So, now our code outputs the following:

Shape Name: Square, Shape Type: Quadrilateral
Area: 64
Shape Name: Rectangle, Shape Type: Quadrilateral
Area: 80


In this article we have covered the concepts of OOP, Objects & Classes, Encapsulation and Inheritance. We will cover the next topics (Abstraction and Polymorphism) in the next article.


Comments

Popular posts from this blog

OOP with Python - Part 2

Data Structures - Part 2