Python OOP Concepts: Mastering Object-Oriented Programming

Learn the fundamentals of Object-Oriented Programming (OOP) in Python. Discover how OOP principles, such as encapsulation, inheritance, and polymorphism, enhance programming by modeling real-world entities. Understand the transition from procedural programming to OOP and how it improves code structure and reusability.



Python - OOP Concepts

OOP stands for Object-Oriented Programming. It is a programming model based on the concept of "objects" which are data structures encapsulated with data and behavior. This chapter will help you master OOP in Python.

Procedural Oriented Approach

Early programming languages were procedural. They consist of a series of instructions in a logical order. Functions were used to break down complex programs into smaller, reusable blocks. However, this approach had several problems:

  • Top-down approach makes maintenance difficult.
  • Uses a lot of global data items, increasing memory overhead.
  • More importance on process than data.
  • Unrestricted movement of data across functions.

Python - OOP Concepts

In real life, we deal with objects like students, employees, cars, etc. Objects have attributes (data) and behavior (functions) associated with them. In OOP, a class defines these attributes and behaviors. An object is an instance of a class.

Principles of OOP

  • Class
  • Object
  • Encapsulation
  • Inheritance
  • Polymorphism

Class & Object

A class is a blueprint for objects. An object is an instance of a class. Below is an example:


    # defining class
    class Smartphone:
       # constructor    
       def __init__(self, device, brand):
          self.device = device
          self.brand = brand
       
       # method of the class
       def description(self):
          return f"{self.device} of {self.brand} supports Android 14"
    
    # creating object of the class
    phoneObj = Smartphone("Smartphone", "Samsung")
    print(phoneObj.description())
                
Output

    Smartphone of Samsung supports Android 14
                

Encapsulation

Encapsulation restricts direct access to some of an object's components, which can prevent the accidental modification of data. Below is an example:


    class Desktop:
       def __init__(self):
          self.__max_price = 25000
    
       def sell(self):
          return f"Selling Price: {self.__max_price}"
    
       def set_max_price(self, price):
          if price > self.__max_price:
             self.__max_price = price
    
    # Object
    desktopObj = Desktop()
    print(desktopObj.sell()) 
    
    # modifying the price directly
    desktopObj.__max_price = 35000
    print(desktopObj.sell()) 
    
    # modifying the price using setter function
    desktopObj.set_max_price(35000)
    print(desktopObj.sell())
                
Output

    Selling Price: 25000
    Selling Price: 25000
    Selling Price: 35000
                

Inheritance

Inheritance allows a class to inherit attributes and methods from another class. Below is an example:


    # define parent class
    class Parent:        
       parentAttr = 100
       def __init__(self):
          print("Calling parent constructor")
    
       def parentMethod(self):
          print("Calling parent method")
    
       def setAttr(self, attr):
          Parent.parentAttr = attr
    
       def getAttr(self):
          print("Parent attribute:", Parent.parentAttr)
    
    # define child class
    class Child(Parent): 
       def __init__(self):
          print("Calling child constructor")
    
       def childMethod(self):
          print("Calling child method")
    
    # instance of child
    c = Child()  
    # child calls its method        
    c.childMethod() 
    # calls parent's method     
    c.parentMethod()  
    # again call parent's method   
    c.setAttr(200)  
    # again call parent's method     
    c.getAttr()          
                
Output

    Calling child constructor
    Calling child method
    Calling parent method
    Parent attribute: 200
                

Polymorphism

Polymorphism allows methods to do different things based on the object it is acting upon. Below is an example:


    # define parent class
    class Parent:        
       def myMethod(self):
          print("Calling parent method")
    
    # define child class
    class Child(Parent): 
       def myMethod(self):
          print("Calling child method")
    
    # instance of child
    c = Child()
    # child calls overridden method          
    c.myMethod()
                
Output

    Calling child method
                

Operator Overloading

Operator overloading allows custom implementation for operators in user-defined classes. Below is an example:


    class Vector:
       def __init__(self, a, b):
          self.a = a
          self.b = b
    
       def __str__(self):
          return 'Vector (%d, %d)' % (self.a, self.b)
       
       def __add__(self, other):
          return Vector(self.a + other.a, self.b + other.b)
    
    v1 = Vector(2, 10)
    v2 = Vector(5, -2)
    print(v1 + v2)
                
Output

    Vector (7, 8)