Python OOPs Concepts

Object-oriented Programming (OOPs) is a programming paradigm in Python that employs objects and classes. It aims to incorporate real-world entities such as inheritance, polymorphisms, encapsulation, and so on into programming. The main idea behind OOPs is to bind the data and the functions that work on it as a single unit so that no other part of the code can access it.

The object is associated with real-world entities such as a book, a house, a pencil, and so on. The oops concept is concerned with writing reusable code. It is a common technique for solving problems by creating objects.

Fundamental Concepts of Object-oriented Programming (OOPs)

  • Class
  • Object
  • Method
  • Inheritance
  • Polymorphism
  • Data Abstraction
  • Encapsulation

Python OOPs Concepts

Class

A class is a collection of objects, or a blueprint of objects that defines the common attributes and methods. The question now is, how do you do that?

It logically groups the data in such a way that code reusability is made simple. Consider an office going ’employee’ as a class and all the attributes related to it like ’empName,’ ’empAge,’ ’empSalary,’ and ’empId’ as Python objects.

Syntax:

class ClassName: 
<statement-1> 
.

.
<statement-N>

Example: Create an empty class

# Create an empty class say Btechgeeks
class Pythonprograms:
    pass

Object

The object is an entity that has a state and behavior. The object is a stateful entity with behaviour. It could be any real-world object, such as a mouse, keyboard, chair, table, pen, etc.

In Python, everything is an object, and almost everything has attributes and methods. All functions have a built-in attribute __doc__, which returns the docstring defined in the function source code.

When we define a class, we must create an object to allocate memory.

An object is made up of:

  • state: The state is represented by the attributes of an object. It also reflects an object’s properties.
  • behavior: The behavior is represented by the methods of an object. It also represents an object’s response to other objects.
  • Identity: It gives an object a unique name and allows it to interact with other objects.

Let us consider the class dog to better understand its state, behaviour, and identity:

  • The identity can be thought of as the dog’s name.
  • The breed, age, and color of the dog are examples of state or attributes.
  • The behavior can be used to determine whether the dog is eating or sleeping.

Example

# create a class say Employ
class Employ:  
    def __init__(self, empName, empId):  
        self.empName = empName  
        self.empId = empId  
    def displayEmpDetails(self):  
        print(self.empName,self.empId)  

# calling the Employ class by passing the empName, empId as arguments
emp_object = Employ("Nick", 2122)  
# Display the employ details by calling the displayEmpDetails()
# method using the emp_object
emp_object.displayEmpDetails()  

Output:

Nick 2122

Method

A method is a function that is linked to an object. A method in Python is not unique to class instances. Methods can exist on any object type.

__init__ method:

The __init__ method is similar to C++ and Java constructors. It is executed whenever a class object is instantiated. The method is useful for performing any initialization on your object.

Inheritance

The most important aspect of object-oriented programming is inheritance, which simulates the real-world concept of inheritance. It specifies that the child object inherits/acquires all of the parent object’s properties and behaviors.

We can use inheritance to create a class that inherits all of the properties and behavior of another class. The new class is referred to as a derived class or child class, while the class whose properties are acquired is referred to as a base class or parent class.

It ensures that the code can be reused.

There are 4 types of inheritance in python. They are:

1)Single Inheritance:

A derived class can inherit characteristics from a single-parent class through single-level inheritance.

# Create a parent calss say Employ()
class Employ():
  def __init__(self, empName, empId):  
    self.empName = empName
    self.empId = empId

# Create a child calss say EmployChildClass()
class EmployChildClass(Employ):
  def __init__(self, empName, empId, empRole):
    self.empName = empName
    self.empId = empId
    self.empRole = empRole

# Calling the above Employ parent class by passing empName, empId, empRole
# as arguments to it
emp_object = Employ('Isha', 2122)

# Printing the empId using the emp_object
print(emp_object.empId)

Output:

2122

2)Multi-level Inheritance:

Multi-level inheritance allows a derived class to inherit properties from an immediate parent class, which in turn inherits properties from his parent class.

# Create a parent calss say Employ()
class Employ():
  def __init__(self, empName, empId):  
    self.empName = empName
    self.empId = empId

# Create a first child class say EmployChildClass_1() 
# inheriting properties from parent class Employ
class EmployChildClass_1(Employ):
  def __init__(self, empName, empId):  
    self.empName = empName
    self.empId = empId

# Create a second child class say EmployChildClass_2() 
# inheriting properties from first child class EmployChildClass_1 
class EmployChildClass_2(EmployChildClass_1):
   def __init__(self, empName, empId):  
    self.empName = empName
    self.empId = empId

employ_1 = Employ('Nick', 2122)
employ_2 = EmployChildClass_1('John', 2123)
 
print(employ_1.empId)
print(employ_2.empId)

Output:

2122
2123

3)Hierarchical Inheritance:

More than one derived class can inherit properties from a parent class using hierarchical-level inheritance.

# Create a parent calss say Employ()
class Employ():
  def __init__(self, empName, empId):  
    self.empName = empName
    self.empId = empId

# Create a first child class say EmployChildClass_1() 
# inheriting properties from parent class Employ
class EmployChildClass_1(Employ):
  def __init__(self, empName, empId):  
    self.empName = empName
    self.empId = empId

# Create a second child class say EmployChildClass_2() 
# inheriting properties from parent class Employ
class EmployChildClass_2(Employ):
   def __init__(self, empName, empId):  
    self.empName = empName
    self.empId = empId

employ_1 = Employ('Nick', 2122)
employ_2 = Employ('John', 2123)
 
print(employ_1.empId)
print(employ_2.empId)

Output:

2122
2123

4)Multiple Inheritance:

Multiple inheritance allows a single derived class to inherit properties from multiple base classes.

# Create a parent class say Employ_1()
class Employ_1():
    def __init__(self, empName, empId):  
      self.empName = empName
      self.empId = empId

# Create another parent class say Employ_2()
class Employ_2():
    def __init__(self, empName, empId, empRole):
      self.empName = empName
      self.empId = empId
      self.empRole = empRole
 
# Create a child class say EmployChildClass() 
# inheriting properties from both the parent classes
class EmployChildClass(Employ_1, Employ_2):
    def __init__(self, empName, empId, empRole):
      self.empName = empName
      self.empId = empId
      self.empRole = empRole

employ1 = Employ_1('Nick', 2122)
employ2 = Employ_2('John', 2123, 'Analyst')
 
print(employ1.empId)
print(employ2.empRole)

Output:

2122
Analyst

Polymorphism

Polymorphism is derived from the Greek words poly (many) and morphism (forms). That is, the same function name can be used for many types. This makes programming more intuitive and simple.

Polymorphism means that the same task can be performed in different ways. For example, suppose you have a class animal and all of the animals speak. But they communicate in different ways. In this case, the “speak” behavior is polymorphic and varies depending on the animal. So, while the abstract concept of “animal” does not actually “speak,” specific animals (such as dogs and cats) have a concrete implementation of the action “speak.”

A child class inherits all of the parent class’s methods. In other cases, however, the method inherited from the parent class does not fully fit into the child class. In such circumstances, you must re-implement the method in the child class.

Example

# Create a parent class say Vehicle
class Vehicle:

    def introduction(self):
        print("Vehicles have different models")

    def categories(self):
        print("some are two-wheelers and some four-wheelers etc")

# Create a child class say Car() inheriting properties the parent class Vehicle
class Car(Vehicle):

    def categories(self):
        print("Car is a four-wheeler")

# Create another child class say Bike() inheriting properties the parent class Vehicle
class Bike(Vehicle):

    def categories(self):
        print("Bike is a two-wheeler")

Vehicle_object = Vehicle()
Car_object = Car()
Bike_object = Bike()

Vehicle_object.introduction()
Vehicle_object.categories()
print()
Car_object.introduction()
Car_object.categories()
print()
Bike_object.introduction()
Bike_object.categories()

Output:

Vehicles have different models
some are two-wheelers and some four-wheelers etc

Vehicles have different models
Car is a four-wheeler

Vehicles have different models
Bike is a two-wheeler

Polymorphism is classified into two types:

  • Compile-time Polymorphism.
  • Run-time Polymorphism

Compile-time Polymorphism:

A compile-time polymorphism, also known as a static polymorphism, is one that is resolved during the program’s compilation. “Method overloading” is a common example.

Run-time Polymorphism:

A running time Polymorphism is also known as dynamic polymorphism because it is resolved during the run time. “Method overriding” is a common example of Run-time polymorphism.

Encapsulation

Encapsulation is the process of wrapping up variables and methods into a single entity. It is a fundamental idea in object-oriented programming (OOP). It works as a protective shield, limiting direct access to variables and methods and preventing accidental or unauthorized data alteration. Encapsulation also turns objects into more self-sufficient, self-contained (independently functioning)units.

Real-time Example

Consider the following real-world example of encapsulation. A company has various sections, such as the accounts and finance sections. The finance department controls all financial transactions and data. The sales department is also in charge of all sales-related activities. They keep track of every sale. A finance officer may require full sales data for a certain month at times. He is not permitted to see the data from the sales area in this case. He must first call another officer in the sales unit to request the data. This is called encapsulation.

Access Modifiers in Encapsulation

While programming, it may be necessary to restrict or limit access to particular variables or functions. This is where access modifiers come into play.

When it comes to access, there are three types of access specifiers that can be utilised while executing Encapsulation in Python. They are:

  • Public Members
  • Private Members
  • Protected Members

Public Members

Members of the public can be accessed from anywhere from a class. By default, all of the class variables are public.

# create a class say employDetails
class employDetails:

    # Create a __init_ constructor which accepts employName and employId as arguments 
    def __init__(self, employName, employId):
        # create public data members
        self.employName = employName;
        self.employId = employId;

    # Create a function say displayEmployId which prints 
    # the employ id when it is called
    def displayEmployId(self): 
        # Access the public data member of the class and print the result
        print("employId: ", self.employId)

# create an object of the class employDetails
employdetails_obj = employDetails("John", 2122);

# Access the public data member of the class and print the result
print("employName: ", employdetails_obj.employName)  

# call the public member function displayEmployId of the class employDetails
employdetails_obj.displayEmployId()

Output:

employName: John
employId: 2122

The variable employName and employId are both public in class employDetails. These data members are available throughout the program.

Private Members

Private members are accessible within the class. To define a private member, use a double underscore “__” before the member name.

Private members and protected members are the same thing. The difference is that private class members should not be accessed by anyone outside the class or its base classes. Private instance variable variables that can be accessed outside of a class do not exist in Python.

Python’s private and secured members can be accessed from outside the class by renaming them.

# create a class say employDetails
class employDetails:
   
   # Create a __init_ constructor which accepts employName and employId as arguments 
   def __init__(self, employName, employId):
      # Create a public variable
      self.employName = employName
      # Create private variable prefixed with "__"
      self.__employId = employId

# create an object of the class employDetails and call it
employdetails_obj = employDetails("John", 2122)

# Access the public variable name of the class and print the result
print("employName:",employdetails_obj.employName)

# Access the private data variable __employId of the class employDetails and print the result
print("employId:",employdetails_obj._employDetails__employId)

Output:

employName: John
employId: 2122

__employId is a private variable in class employDetails; thus, it is accessed by creating an object/instance.

Protected Members

Protected members can be accessed both within the class and by its subclasses. To define a protected member, use a single underscore “_” before the member name.

The protected variable can be accessed from the class and in derived classes (it can even be updated in derived classes), but it is not usually accessed outside of the class body.

The __init__ method(constructor), runs when an object of a type is instantiated.

# create a class say employDetails
class employDetails:
    # Creat which accepts employName and employId as arguments  
    def __init__(self, employName, employId):
        # protected data members as they are prefixed with single underscore "_"
        self._employName = employName
        self._employId = employId

# create object of the class employDetails and call it 
employdetails_obj = employDetails("John", 2122)

# Access the protected members of the class and print the result
print("employName:",employdetails_obj._employName)
print("employId:",employdetails_obj._employId)

Output:

employName: John
employId: 2122

Abstraction

Abstraction hides the unnecessary code/internal details and shows only functionalities. Also, when we do not want to reveal sensitive parts of our code implementation, data abstraction comes into play.

Data abstraction in Python is accomplished by creating abstract classes.

Real-time example

Assume you purchased a movie ticket from BookMyShow via net banking or another method. You have no idea how the pin is generated or how the verification is carried out. This is known as ‘abstraction’ in programming, and it basically means that you only show the implementation details of a specific process and hide the details from the user. It is used to simplify complex problems by modeling problem-specific classes.