Category: Python OOPs

  • Method Overloading in Python

    Method overloading, as known in many object-oriented programming languages, refers to defining more than one methods with the same name but different sets of arguments.

    Unlike Java or C++Python does not support method overloading in its traditional form. However, we can achieve similar functionality with the help of other ways possible.

    Why Doesn’t Python Support Method Overloading?

    Traditional method overloading is not possible in Python due to the support for dynamic typing and variable-length arguments. If a class consisting of multiple methods sharing same name, the most recently defined method will overwrite the rest of the methods.

    This is because the functions in Python are treated as objects and are stored in a class’s internal dictionary, where the name of the method serves as the key and the function object as the value. As a result, redefining a method simply replaces the previous one.

    Therefore, redefining a method results in replacing the stored function object with the updated one.

    Example: Traditional Method Overloading (Not Supported)

    Let us take an exampe to demonstrate the method overloading in Python.

    Example

    # traditional method overloading  
    
      
    
    # creating a class  
    
    class Example:  
    
      # show() method with single argument  
    
      def show(self, a):  
    
        print(f"Single argument: {a}")  
    
      
    
      # show() method with two arguments  
    
      def show(self, a, b):  
    
        print(f"Two arguments: {a}, {b}")  
    
      
    
    # instantiating the class  
    
    obj = Example()  
    
    # calling the method  
    
    obj.show(5)

    Output:

    TypeError: Example.show() missing 1 required positional argument: 'b'

    In this example, we have created a class ‘Example’ consisting of two show() methods. The first method is replaced by the second one implying that the first one is not accessible anymore.

    How to perform Method Overloading in Python?

    Since, the method overloading in Python cannot be done traditionally, there are few other ways to achieve method overloading, including:

    • Using Default Parameter Values
    • Using *args
    • Using @singledispatch from functools
    • Using @dispatch from multipledispatch

    Let us understand each of these methods with the help of examples.

    Method 1: Using Default Parameter Values

    In Python, we are allowed to assign default values to the methods’ parameters. This lets a single method behave differently depending on how many arguments are passed. This way we can effectively simulate overloading in Python.

    Let us see an example showing the use of default parameter values.

    Example

    # creating a class  
    
    class Simple_Calculator:  
    
      # defining a function to add numbers  
    
      def add(self, a, b = 0, c = 0):  
    
        ''''' 
    
        This method has few default parameter values set to 0 
    
        '''  
    
        return a + b + c  
    
      
    
    # creating an object  
    
    my_calculator = Simple_Calculator()  
    
      
    
    # calling the add() method  
    
    print(my_calculator.add(5)) # single argument  
    
    print(my_calculator.add(5, 10)) # two arguments  
    
    print(my_calculator.add(5, 10, 15)) # three arguments

    Output:

    5
    15
    30
    

    Explanation:

    The add() method is adaptable as it uses default values for parameters b and c. When the method is called with only one argument, thus b and c default to 0. When two arguments are provided, only c defaults to 0.

    While this approach is functional and flexible, it fails to achieve true overloading as Python treats it as a single method with optional parameters, unlike some other languages that support multiple methods with the same name.

    Method 2: Using *args (Variable-length Arguments)

    Python provides tools like *args that allow a single method to accept a variable number of positional arguments. This way we can simulate method overloading by writing logic that behaves differently on the basis of the number or type of arguments passed.

    We will now see an example.

    Example

    # creating a class  
    
    class Simple_Calculator:  
    
      # defining a function to add numbers  
    
      def add(self, *args):  
    
        ''''' 
    
        This method uses positional arguments 
    
        '''  
    
        return sum(args)  
    
      
    
    # creating an object  
    
    my_calculator = Simple_Calculator()  
    
      
    
    # calling the add() method  
    
    print(my_calculator.add(6)) # single argument  
    
    print(my_calculator.add(6, 12)) # two arguments  
    
    print(my_calculator.add(6, 12, 18)) # three arguments

    Output:

    6
    18
    36
    

    Explanation:

    In this example, the add() method is highly flexible as we are using *args. This allows us to pass any number of arguments to the method, making it easier to calculate sum of multiple values without defining multiple method versions.

    While this improves the flexibility of the add() method, it can lead to unintentional errors due to lack of control over the types of provided parameters – a relative lack of type control could result in surprising problems further down the line.

    Since there is no strict distinction between the types of arguments, the method becomes harder to manage and debug – sacrificing clarity and potentially performance in the process.

    Method 3: Using @singledispatchmethod from functools

    In Python, @singledispatchmethod is a decorator from the functools module that helps us overload methods on the basis of the type of the first argument, especially within the class methods.

    Let us look at the following to understand the working of the @singledispatchmethod decorator.

    Example

    # importing the required attribute from module  
    
    from functools import singledispatchmethod  
    
      
    
    # creating a class  
    
    class Simple_Calculator:  
    
      
    
      # defining a method to calculate sum of two numbers  
    
      @singledispatchmethod  
    
      def add(self, a, b):  
    
        # raising error for unsupported data type  
    
        raise NotImplementedError("Unsupported Data Type")  
    
      
    
      # method overloading for variables of int data type  
    
      @add.register(int)  
    
      def _(self, a: int, b: int) -> int:  
    
        return a + b  
    
      # method overloading for variables of float data type  
    
      @add.register(float)  
    
      def _(self, a: float, b: float) -> float:  
    
        return a + b  
    
      
    
    # creating an object  
    
    my_calculator = Simple_Calculator()  
    
      
    
    # calling the add() method  
    
    print(my_calculator.add(9, 14)) # only int variables  
    
    print(my_calculator.add(3.72, 1.54))  # only float variables  
    
    try:  
    
      print(my_calculator.add("tpoint", "tech")) # trying to pass str  
    
    except NotImplementedError as e:  
    
      print(e)

    Output:

    23
    5.26
    Unsupported Data Type
    

    Explanation:

    In this example, we have used the @singledispatchmethod decorator to overload the add() method on the basis of the type of the arguments. We have handled int and float inputs, and raised an error for any unsupported data types (e.g., str).

    Method 4 (Optional): Using @dispatch from multipledispatch

    Apart from the three discussed earlier, there is one another method that we can use to achieve method overloading. This requires us to install a third-party library known as multipledispatch.

    This library enables true multiple dispatch, meaning we can select a function or method implementation on the basis of the data types of all parameters, not just the first one.

    To install the multipledispatch method, we can use pip installer as shown below:

    Syntax:

    $ pip install multipledispatch  

    Now let us see an example to understand the use of @dispatch property offered by this module.

    Example

    # importing the required attribute from module  
    
    from multipledispatch import dispatch  
    
      
    
    # creating a class  
    
    class Simple_Calculator:  
    
      
    
      # defining a method to calculate sum of two numbers  
    
      
    
      # method overloading for only int data type  
    
      @dispatch(int, int)  
    
      def add(self, a, b):  
    
        return a + b  
    
      
    
      # method overloading for only float data type  
    
      @dispatch(float, float)  
    
      def add(self, a, b):  
    
        return a + b  
    
      
    
      # method overloading for first int then float  
    
      @dispatch(int, float)  
    
      def add(self, a, b):  
    
        return a + b  
    
      
    
      # method overloading for first float then int  
    
      @dispatch(float, int)  
    
      def add(self, a, b):  
    
        return a + b  
    
      
    
    # creating an object  
    
    my_calculator = Simple_Calculator()  
    
      
    
    # calling the add() method  
    
    print(my_calculator.add(9, 14)) # int + int  
    
    print(my_calculator.add(3.72, 1.54))  # float + float  
    
    print(my_calculator.add(12, 5.2))  # int + float  
    
    print(my_calculator.add(1.7, 0.56))  # float + int

    Output:

    23
    5.26
    17.2
    2.26
    

    Explanation:

    In this example, the dispatch creates an object which stores different implementation. During runtime, it selects the appropriate method as the type and number of arguments passed.

    Note: @dispatch from multipledispatch is not a built-in Python feature. It comes from an external library and may not be ideal for performance-critical applications.

    Conclusion

    In this example, we learn what method overloading is and the reason why Python does not support traditional method overloading. In order to achieve method overloading with Python, we discussed several approaches including – the use of default values for parameters or positional arguments, and decorators like @singledispatch from functools and @dispatch from the multipledispatch module. We also learned how these approaches help delivering the similar effect.

  • Access Modifiers in Python

    Access Modifiers are used by various programming languages like C++, Java, Python, etc., that follow the object-oriented paradigm. Access Modifiers are used to modify the access of the class member variables and methods from outside the class. Encapsulation is a principle of OOPs that protects the internal data of the class with the help of access modifiers such as Public, Protected, and Private.

    In the following tutorial, we will learn about the Access Modifiers and their types. We will also discuss how to use them in the Python programming language.

    Types of Access Modifiers

    Python offers three levels of access modifiers:

    1. Public (name): The members of the public access modifier can be accessed anywhere.
    2. Protected (_name): The members of the protected access modifier should be accessed only within the class and its subclasses. It is not intended for public use.
    3. Private (__name): The members of the private access modifier can be accessed only within the class (using name mangling).
    Access Modifiers in Python

    Let us discuss these modifiers with the help of examples.

    1. Public Access Modifier

    The member variables and methods of the public access modifier are accessible from anywhere in the program. By default, all the attributes and methods in Python classes are public unless explicitly modified.

    Public Access Modifier Example

    Let us consider the following example of the public access modifier:

    Example

    # defining a class 'Animal'    
    
    class Animal:    
    
      def __init__(self, name, home):    
    
        self.name = name  # public attribute    
    
        self.home = home  # public attribute    
    
        
    
      # public method    
    
      def display_data(self):    
    
        print(self.name, "lives in", self.home)    
    
        
    
    # instantiating the class    
    
    wild_animal = Animal("Lion", "Den")    
    
        
    
    # printing the values of the public attributes    
    
    print(wild_animal.name)    
    
    print(wild_animal.home)    
    
        
    
    # calling the method to print details    
    
    wild_animal.display_data()

    Output:

    Lion
    Den
    Lion lives in Den
    

    Explanation:

    In the above snippet of code, we have defined a class called Animal. The Animal class has two member variables, name and home and a method display_data() which prints the member variable values. Both of these variables are public as no specific keyword (or convention) is assigned to them. We then instantiated the class as wild_animal. We then printed the values of the public attributes and called the method to print the details of the created object.

    Key Features of Public Access Modifier

    Several key features of Public Access Modifier in Python are as follows:

    1. Members of the public can access the public access modifier from anywhere.
    2. Both attributes and methods can be modified from outside the class.

    2. Protected Access Modifier

    The member variables and methods of the protected access modifier are only accessible within the class where it is declared and their subclasses. In order to implement the protected field or method, the developer follows a particular naming convention, mostly by adding a prefix to the variable or function name.

    Protected members are indicated by a single underscore (_variable). Note that the Python interpreter does not enforce this restriction like other languages; it is only designated for programmers as they would try to access it using a plain name instead of calling it with the help of the respective prefix.

    Protected Access Modifier Example

    Let us consider the following example of a protected access modifier.

    Example

    # defining a class named Animal  
    
    class Animal:    
    
      def __init__(self, name, home):    
    
        self._name = name  # protected attribute    
    
        self._home = home  # protected attribute    
    
        
    
      # protected method    
    
      def _display_data(self):    
    
        print(self._name, "lives in", self._home)    
    
        
    
    # instantiating the class    
    
    wild_animal = Animal("Lion", "Den")    
    
        
    
    # printing the values of the protected attributes    
    
    # Note: Accessing protected members is possible, but it's discouraged    
    
    print(wild_animal._name)    
    
    print(wild_animal._home)    
    
        
    
    # calling the protected method    
    
    wild_animal._display_data()

    Output:

    Lion
    Den
    Lion lives in Den
    

    Explanation:

    In the above snippet of code, we have defined a class called Animal. The Animal class has two member variables, _name and _home and a protected method _display_data() which prints the member variable values. Both of these variables are protected, denoted by a single underscore (_) as a prefix. We then instantiated the class as wild_animal. We then printed the values of the protected attributes and called the method to print the details of the created object.

    Note: It is possible to access the protected members; however, it is discouraged.

    Key Features of Protected Access Modifier

    Several key features of Protected Access Modifier in Python are as follows:

    1. Single underscore (_) is just a convention. It does not prevent access from outside.
    2. The variable can still be accessed and modified from outside the class.
    3. Protected members are often used when designing parent-child class relationships (inheritance), where they can be accessed in a subclass.

    3. Private Access Modifier

    The member variables and methods of the private access modifier are only accessible within the class. Private access modifier is the most secure access modifier. Private members are indicated by double underscores (__) before the variable or method name. Python performs name mangling, meaning that it changes the name of the variable internally to prevent accidental access.

    Private Access Modifier Example

    Let us consider the following example of the private access modifier.

    Example

    # defining a class 'Animal'    
    
    class Animal:    
    
      def __init__(self, name, home):    
    
        self.__name = name  # private attribute    
    
        self.__home = home  # private attribute    
    
        
    
      # private method    
    
      def __display_data(self):    
    
        print(self.__name, "lives in", self.__home)    
    
        
    
    # instantiating the class    
    
    wild_animal = Animal("Lion", "Den")    
    
        
    
    # trying to access private attributes directly (this will raise an AttributeError)    
    
    # print(wild_animal.__name)  # This will cause an error    
    
    # print(wild_animal.__home)  # This will cause an error    
    
        
    
    # trying to call the private method directly (this will also raise an AttributeError)    
    
    # wild_animal.__display_data() # This will also cause an error

    Explanation:

    In the above snippet of code, we have defined a class called Animal. The Animal class has two member variables, __name and __home and a private method __display_data() which prints the member variable values. Both of these variables are protected, denoted by a double underscore (__) as a prefix. We then instantiated the class as wild_animal. We then tried to print the values of the private attributes and called the method to print the details of the created object. However, doing this will raise an error.

    Accessing Private Members using Name Mangling

    We cannot directly access or modify the private attributes from outside the class in Python. The Name Mangling done by Python makes it difficult, but not impossible. It is strongly discouraged to do so, as it breaks encapsulation.

    In name mangling, we can rename the private members of the class from __name to _ClassName__name. Let us consider the following example illustrating the use of name mangling in accessing the private members of the class.

    Example

    # defining a class named Animal    
    
    class Animal:    
    
      def __init__(self, name, home):    
    
        self.__name = name  # private attribute    
    
        self.__home = home  # private attribute    
    
        
    
      # private method    
    
      def __display_data(self):    
    
        print(self.__name, "lives in", self.__home)    
    
        
    
    # instantiating the class    
    
    wild_animal = Animal("Lion", "Den")    
    
        
    
    # accessing private attributes using Name Mangling    
    
    print(wild_animal._Animal__name)    
    
    print(wild_animal._Animal__home)    
    
        
    
    # trying to call the private method using Name Mangling    
    
    wild_animal._Animal__display_data()

    Output:

    Lion
    Den
    Lion lives in Den
    

    Explanation:

    In the above snippet of code, we have defined a class called Animal. The Animal class has two member variables, __name and __home and a private method __display_data() which prints the member variable values. Both of these variables are protected, denoted by a double underscore (__) as a prefix. We then instantiated the class as wild_animal.

    We then used the name mangling where we have renamed the private attributes from __name and __home to _Animal__name and _Animal__home, respectively and printed their values for the users. We then called the method again using the name mangling to print the details of the created object. In this case, the values are printed successfully.

    Key Features of Private Access Modifier

    Several key features of Private Access Modifier in Python are as follows

    1. The members of the Private access modifier cannot be accessed directly from outside the class.
    2. Name mangling grants users access to private members; however, it should be avoided unless necessary.
    3. This ensures better encapsulation and prevents accidental modification of variables.

    Comparison between the Different Access Modifiers in Python

    We will now look at the tabular comparison between public, protected and private access modifiers in Python.

    Access ModifierSyntaxAccessibilityExampleKey Features
    PublicvarNamePublic members are accessible from anywhere (inside and outside the class)self.varNameDefault access modifier in Python.No restrictions on accessing or modifying the public attributes and methods.
    Protected_varNameProtected members are accessible within the class and subclass (It does not enforce this restriction; it simply provides a convention)self._varNameIndicated using a single underscore (_).Attributes and methods can be accessed from outside the class; however, they should be treated as internal.
    Private__varNamePrivate members are accessible only within the class (We can also apply name mangling to access the members)self.__varNameIndicated using double underscores (__).We cannot access the private members directly from outside the class.Attributes and methods can be accessed using name mangling. (For example, _ClassName__varName)

    Conclusion

    In the above tutorial, we have learned the basics of Object-Oriented Programming. We have also discussed the different principles of OOP. We then understood the concept of encapsulation in detail. We have learned that encapsulation, being a fundamental principle of OOP, helps protect the data and ensure controlled access to attributes and methods of the class. Python offers three access modifiers to implement encapsulation in programs. These access modifiers (public, protected, and private) allow programmers to manage data visibility and security.

  • Encapsulation in Python

    In Python, Encapsulation is a fundamental concept of object-oriented programming (OOP). It refers to class as collections of data (variables) and methods that operate on that data.

    Encapsulation restricts some components of an object from being accessed directly, so that unintended interference and data corruption may be prevented.

    How does Encapsulation Work in Python?

    Encapsulation is implemented in Python by stopping users from directly accessing certain parts of an object, while giving them the ability to access those areas through other means (methods).

    Access can be controlled using different access modifiers:

    • Public Attribute: Accessible from anywhere.
    • Protected Attributes (_singleUnderscore): Not intended for public use, but still accessible.
    • Private Attributes (__doubleUnderscore): Not directly accessible from outside the class.
    Member TypeSyntaxAccessible Inside ClassAccessible in SubclassesAccessible Outside Class
    Publicself.varYesYesYes
    Protectedself._varYesYes (Recommended inside subclasses only)Yes (Not recommended)
    Privateself.__varYesNo (Unless using name mangling)No (Direct access restricted)

    Implementation of Encapsulation in Python

    Python uses three levels of access control for class members:

    • public,
    • protected, and
    • private.

    Let’s explore each with examples.

    Public Members

    Public members can be accessed everywhere, inside the class, outside the class, and inside derived (child) classes.

    • Usage: No underscore before the variable name.

    Python Public Members Example

    Let us take an example to demonstrate public members in Python.

    Example

    class Car:  
    
        def __init__(self, brand, model):  
    
            self.brand = brand  # Public attribute  
    
            self.model = model  # Public attribute  
    
      
    
        def display(self):  
    
            print(f"Car: {self.brand} {self.model}")  
    
      
    
    # Creating an object  
    
    car = Car("Toyota", "Corolla")  
    
      
    
    # Accessing public members  
    
    print(car.brand)    
    
    print(car.model)    
    
      
    
    # Calling public method  
    
    car.display()

    Output:

    Toyota
    Corolla
    Car: Toyota Corolla
    

    Explanation:

    Public attributes (brand, model) will also be accessible outside the class. The display() method which is also public can be accessed from other classes.

    Protected Members

    Protected members are indicated by a single underscore (_variable).

    • Usage: It can be accessed outside the class but should only be accessed within the class and subclasses (not enforced, just a convention).

    Python Protected Members Example

    Let us take an example to demonstrate protected members in Python.

    Example

    class Car:  
    
        def __init__(self, brand, model, engine):  
    
            self.brand = brand  # Public attribute  
    
            self._model = model  # Protected attribute  
    
            self._engine = engine  # Protected attribute  
    
      
    
        def _show_details(self):  # Protected method  
    
            print(f"Brand: {self.brand}, Model: {self._model}, Engine: {self._engine}")  
    
      
    
    class ElectricCar(Car):  
    
        def __init__(self, brand, model, battery_capacity):  
    
            super().__init__(brand, model, "Electric")  
    
            self.battery_capacity = battery_capacity  
    
      
    
        def show_info(self):  
    
            self._show_details()  # Accessing protected method from subclass  
    
            print(f"Battery: {self.battery_capacity} kWh")  
    
      
    
    # Creating an object of ElectricCar  
    
    tesla = ElectricCar("Tesla", "Model S", 100)  
    
      
    
    # Accessing protected members from subclass  
    
    tesla.show_info()  
    
      
    
    # Accessing protected members outside the class (not recommended)  
    
    print(tesla._model)  # Works, but not recommended

    Output:

    Brand: Tesla, Model: Model S, Engine: Electric
    Battery: 100 kWh
    Model S
    

    Explanation:

    _model and _engine are protected attributes,_show_details() is a protected method. They can be accessed in subclasses, but it’s not recommended to use them directly outside the class.

    Private Members

    Private members are indicated by double underscores (__variable).

    • Usage: They cannot be accessed directly outside the class.

    Python Private Members Example

    Let us take an example to demonstrate private members in Python.

    Example

    class BankAccount:  
    
        def __init__(self, account_number, balance):  
    
            self.account_number = account_number  # Public attribute  
    
            self.__balance = balance  # Private attribute  
    
      
    
        def get_balance(self):  # Getter method  
    
            return self.__balance  
    
      
    
        def set_balance(self, amount):  # Setter method  
    
            if amount >= 0:  
    
                self.__balance = amount  
    
            else:  
    
                print("Invalid amount! Balance cannot be negative.")  
    
      
    
    # Creating an account object  
    
    account = BankAccount("123456789", 1000)  
    
      
    
    # Accessing public member  
    
    print(account.account_number)  # Works fine  
    
      
    
    # Trying to access private member directly (will raise AttributeError)  
    
    # print(account.__balance)  # Uncommenting this will cause an error  
    
      
    
    # Using getter method to access private attribute  
    
    print(account.get_balance())  # Works fine  
    
      
    
    # Using setter method to update private attribute  
    
    account.set_balance(2000)  
    
    print(account.get_balance())  # Updated balance  
    
      
    
    # Accessing private attribute using name mangling (Not recommended)  
    
    print(account._BankAccount__balance)  # Works, but should be avoided

    Output:

    123456789
    1000
    2000
    2000
    

    Explanation:

    __balance is a private attributedirect access is not allowed. We use getter (get_balance()) and setter (set_balance()) methods to control access. Python renames __balance internally as _BankAccount__balance, allowing access via name mangling (but this is bad practice).

    Conclusion

    Encapsulation hides the internal details and the implementation of the object’s attributes by preventing direct access. In Python, encapsulation is applied through public, protected, and private members for class attributes, and controlling access through getters and setters. It improves the security, maintainability and structure of the code because of the convention-based approach in Python.

  • Abstraction in Python

    Abstraction is one of the core principles of object-oriented programming (OOP) in Python. This way developers hide unnecessary implementation details and expose only the relevant functionalities. For example, users of Learn Python App may find the content easy to understand, but they are unaware of the processes involved in gathering, organizing, and publishing it.

    In Python, data abstraction can be achieved with the help of the abstract classes and methods from the abc module.

    Importance of Abstraction in Python

    Abstraction allows programmers to hide complex implementation details while displaying the users only the essential data and functions. Data abstraction helps making it easier in order to design modular as well as properly structured code. It also helps simplifying the understanding and maintenance of the program, promoting the reusability of code and improving the developer collaboration.

    Types of Data Abstraction in Python

    The abstraction is defined in the following two ways:

    • Abstract classes
    • Abstract Method

    Abstract Class

    An abstract class in Python is a class that comprises at least one abstract method and is not directly instantiable. Abstract methods are classes without a body that defines common behaviors for derived classes. An abstract method cannot exist without its parent class.

    To enforce abstract methods in a particular class, we first need to import the ABC module from abc, inherit from ABC, and use the abstract decorator for methods tagged as abstract.

    Python Abstract Class Example

    Let us take an example to demonstrate the abstract class in python.

    Example

    from abc import ABC, abstractmethod  
    
      
    
    # Abstract class  
    
    class Vehicle(ABC):  
    
             
    
        @abstractmethod  
    
        def start(self):  
    
            pass  # Abstract method with no implementation  
    
         
    
        @abstractmethod  
    
        def stop(self):  
    
            pass  
    
      
    
    # Concrete class implementing the abstract methods  
    
    class Car(Vehicle):  
    
          
    
        def start(self):  
    
            print("Car is starting with a key ignition.")  
    
      
    
        def stop(self):  
    
            print("Car is stopping using the brake.")  
    
      
    
    # Trying to instantiate an abstract class will raise an error  
    
    # vehicle = Vehicle()  # TypeError  
    
      
    
    # Creating an instance of the concrete class  
    
    my_car = Car()  
    
    my_car.start()  
    
    my_car.stop()

    Output:

    Car is starting with a key ignition.
    Car is stopping using the brake.
    

    Explanation:

    Vehicle is an abstract class because it inherits ABC. The methods start() and stop() are abstract. Any subclass is obligated to implement these methods. The Car class actually implements these two methods. If one tries to create an object of Vehicle directly, it will raise a TypeError.

    Abstract Method

    An abstract method in Python is a method declared in a base class, but lack implementation. It serve as a placeholders that derived classes must override. This method ensures a reliable interface across subclasses, promising to provide their own implementation. We can define an abstract method using the @abstractmethod decorator from the abc module.

    Python Abstract Method Example

    Let us take an example to demonstrate the abstract Method in python.

    Example

    from abc import ABC, abstractmethod  
    
      
    
    # Defining an abstract class  
    
    class Animal(ABC):  
    
         
    
        @abstractmethod  
    
        def make_sound(self):  
    
            """Abstract method to be implemented by subclasses"""  
    
            pass  
    
      
    
    # Concrete subclass implementing the abstract method  
    
    class Dog(Animal):  
    
          
    
        def make_sound(self):  
    
            return "Bark!"  
    
      
    
    # Concrete subclass implementing the abstract method  
    
    class Cat(Animal):  
    
          
    
        def make_sound(self):  
    
            return "Meow!"  
    
      
    
    # Trying to instantiate an abstract class will raise an error  
    
    # animal = Animal()  # This will raise TypeError  
    
      
    
    # Creating objects of concrete classes  
    
    dog = Dog()  
    
    cat = Cat()  
    
      
    
    print(dog.make_sound())    
    
    print(cat.make_sound())

    Output:

    Bark!
    Meow!
    

    Explanation:

    The make_sound() method on the Animal class lets it qualify as an abstract class and, hence, must defined by subclasses. Dog and Cat classes are inherited from Animal and return “Bark!” and “Meow!” for make_sound(), respectively. Hence they override it. A TypeError can be raised when trying to instantiate Animal directly.

    Conclusion

    In this tutorial, we learned about Data abstraction. In Python, abstraction offers a means to define a blueprint for a set of related classes by specifying a common structure through abstract methods. As discussed here, using the abc module we can create abstract classes that cannot be instanced directly, forcing the usage of concrete classes that implement the defined abstract methods. This promotes coherence for code, encourages reuse, and enhances the maintainability on object-oriented programming.

  • Python Inheritance

    Inheritance is an important aspect of the object-oriented paradigm. Inheritance provides code reusability to the program because we can use an existing class to create a new class instead of creating it from scratch.

    In inheritance, the child class acquires the properties and can access all the data members and functions defined in the parent class. A child class can also provide its specific implementation to the functions of the parent class. In this section of the tutorial, we will discuss inheritance in detail.

    In python, a derived class can inherit base class by just mentioning the base in the bracket after the derived class name. Consider the following syntax to inherit a base class into the derived class.

    Python Inheritance

    Python Inheritance Syntax

    It has the following syntax:

    class derived-class(base class):  
    
        <class-suite>

    A class can inherit multiple classes by mentioning all of them inside the bracket. Consider the following syntax.

    Syntax

    class derive-class(<base class 1>, <base class 2>, ..... <base class n>):  
    
        <class - suite>

    Python Inheritance Example

    Let us take an example to demonstrate the inheritance in Python.

    class Animal:  
    
        def speak(self):  
    
            print("Animal Speaking")  
    
    #child class Dog inherits the base class Animal  
    
    class Dog(Animal):  
    
        def bark(self):  
    
            print("dog barking")  
    
    d = Dog()  
    
    d.bark()  
    
    d.speak()

    Output:

    dog barking
    Animal Speaking
    

    Python Multi-Level inheritance

    Multi-Level inheritance is possible in python like other object-oriented languages. Multi-level inheritance is archived when a derived class inherits another derived class. There is no limit on the number of levels up to which, the multi-level inheritance is archived in python.

    Python Inheritance

    Python Multi-level Inheritance Syntax

    It has the following syntax:

    class class1:  
    
        <class-suite>   
    
    class class2(class1):  
    
        <class suite>  
    
    class class3(class2):  
    
        <class suite>  
    
    .  
    
    .

    Python Multi-level Inheritance Example

    Let us take an exampel to demonstrate the multi-level inheritance in Python.

    class Animal:  
    
        def speak(self):  
    
            print("Animal Speaking")  
    
    #The child class Dog inherits the base class Animal  
    
    class Dog(Animal):  
    
        def bark(self):  
    
            print("dog barking")  
    
    #The child class Dogchild inherits another child class Dog  
    
    class DogChild(Dog):  
    
        def eat(self):  
    
            print("Eating bread...")  
    
    d = DogChild()  
    
    d.bark()  
    
    d.speak()  
    
    d.eat()

    Output:

    dog barking
    Animal Speaking
    Eating bread...
    

    Python Multiple inheritance

    Python provides us the flexibility to inherit multiple base classes in the child class.

    Python Inheritance

    Python Multiple Inheritance Syntax

    It has the following syntax:

    class Base1:  
    
        <class-suite>  
    
      
    
    class Base2:  
    
        <class-suite>  
    
    .  
    
    .  
    
    .  
    
    class BaseN:  
    
        <class-suite>  
    
      
    
    class Derived(Base1, Base2, ...... BaseN):  
    
        <class-suite>

    Python Multiple Inheritance Example

    Let us take an example to illustrate the multiple inheritance in Python.

    class Calculation1:  
    
        def Summation(self,a,b):  
    
            return a+b;  
    
    class Calculation2:  
    
        def Multiplication(self,a,b):  
    
            return a*b;  
    
    class Derived(Calculation1,Calculation2):  
    
        def Divide(self,a,b):  
    
            return a/b;  
    
    d = Derived()  
    
    print(d.Summation(10,20))  
    
    print(d.Multiplication(10,20))  
    
    print(d.Divide(10,20))

    Output:

    30
    200
    0.5
    

    The issubclass(sub,sup) method

    The issubclass(sub, sup) method is used to check the relationships between the specified classes. It returns true if the first class is the subclass of the second class, and false otherwise.

    Python issubclass(sub, sup) method Example

    Let us take an example to demonstrate the issubclass(sub, sup) method in Python.

    class Calculation1:  
    
        def Summation(self,a,b):  
    
            return a+b;  
    
    class Calculation2:  
    
        def Multiplication(self,a,b):  
    
            return a*b;  
    
    class Derived(Calculation1,Calculation2):  
    
        def Divide(self,a,b):  
    
            return a/b;  
    
    d = Derived()  
    
    print(issubclass(Derived,Calculation2))  
    
    print(issubclass(Calculation1,Calculation2))

    Output:

    True
    False
    

    The isinstance (obj, class) method

    The isinstance() method is used to check the relationship between the objects and classes. It returns true if the first parameter, i.e., obj is the instance of the second parameter, i.e., class.

    Python isinstance (obj, class) Method Example

    Let us take an example to demonstrate the isinstance (obj, class) Method in Python.

    class Calculation1:  
    
        def Summation(self,a,b):  
    
            return a+b;  
    
    class Calculation2:  
    
        def Multiplication(self,a,b):  
    
            return a*b;  
    
    class Derived(Calculation1,Calculation2):  
    
        def Divide(self,a,b):  
    
            return a/b;  
    
    d = Derived()  
    
    print(isinstance(d,Derived))

    Output:

    True
    

    Method Overriding

    We can provide some specific implementation of the parent class method in our child class. When the parent class method is defined in the child class with some specific implementation, then the concept is called method overriding. We may need to perform method overriding in the scenario where the different definition of a parent class method is needed in the child class.

    Python Method Overriding Example

    Consider the following example to perform method overriding in python.

    class Animal:  
    
        def speak(self):  
    
            print("speaking")  
    
    class Dog(Animal):  
    
        def speak(self):  
    
            print("Barking")  
    
    d = Dog()  
    
    d.speak()

    Output:

    Barking
    

    Real Life Example of method overriding

    class Bank:  
    
        def getroi(self):  
    
            return 10;  
    
    class SBI(Bank):  
    
        def getroi(self):  
    
            return 7;  
    
      
    
    class ICICI(Bank):  
    
        def getroi(self):  
    
            return 8;  
    
    b1 = Bank()  
    
    b2 = SBI()  
    
    b3 = ICICI()  
    
    print("Bank Rate of interest:",b1.getroi());  
    
    print("SBI Rate of interest:",b2.getroi());  
    
    print("ICICI Rate of interest:",b3.getroi());

    Output:

    Bank Rate of interest: 10
    SBI Rate of interest: 7
    ICICI Rate of interest: 8
    

    Data abstraction in python

    Abstraction is an important aspect of object-oriented programming. In python, we can also perform data hiding by adding the double underscore (___) as a prefix to the attribute which is to be hidden. After this, the attribute will not be visible outside of the class through the object.

    Python Data Abstraction Example 

    Let us take an example to demonstrate the data abstraction in Python.

    class Employee:  
    
        __count = 0;  
    
        def __init__(self):  
    
            Employee.__count = Employee.__count+1  
    
        def display(self):  
    
            print("The number of employees",Employee.__count)  
    
    emp = Employee()  
    
    emp2 = Employee()  
    
    try:  
    
        print(emp.__count)  
    
    finally:  
    
        emp.display()

    Output:

    The number of employees 2
    AttributeError: 'Employee' object has no attribute '__count'
  • Python Constructor

    A constructor is a special type of method (function) which is used to initialize the instance members of the class.

    In C++ or Java, the constructor has the same name as its class, but it treats constructor differently in Python. It is used to create an object.

    Types of Constructor

    Constructors can be of two types.

    1. Parameterized Constructor
    2. Non-parameterized Constructor

    Constructor definition is executed when we create the object of this class. Constructors also verify that there are enough resources for the object to perform any start-up task.

    Creating the constructor in python

    In Python, the method the __init__() simulates the constructor of the class. This method is called when the class is instantiated. It accepts the self-keyword as a first argument which allows accessing the attributes or method of the class.

    We can pass any number of arguments at the time of creating the class object, depending upon the __init__() definition. It is mostly used to initialize the class attributes. Every class must have a constructor, even if it simply relies on the default constructor.

    Python Example to Create the Constructor

    Consider the following example to initialize the Employee class attributes.

    class Employee:  
    
        def __init__(self, name, id):  
    
            self.id = id  
    
            self.name = name  
    
      
    
        def display(self):  
    
            print("ID: %d \nName: %s" % (self.id, self.name))  
    
      
    
      
    
    emp1 = Employee("John", 101)  
    
    emp2 = Employee("David", 102)  
    
      
    
    # accessing display() method to print employee 1 information  
    
      
    
    emp1.display()  
    
      
    
    # accessing display() method to print employee 2 information  
    
    emp2.display()

    Output:

    ID: 101
    Name: John
    ID: 102
    Name: David
    

    Counting the number of objects of a class

    The constructor is called automatically when we create the object of the class. Consider the following example.

    Example

    class Student:    
    
        count = 0    
    
        def __init__(self):    
    
            Student.count = Student.count + 1    
    
    s1=Student()    
    
    s2=Student()    
    
    s3=Student()    
    
    print("The number of students:",Student.count)

    Output:

    The number of students: 3
    

    Python Non-Parameterized Constructor

    The non-parameterized constructor uses when we do not want to manipulate the value or the constructor that has only self as an argument. Consider the following example.

    Example

    class Student:  
    
        # Constructor - non parameterized  
    
        def __init__(self):  
    
            print("This is non parametrized constructor")  
    
        def show(self,name):  
    
            print("Hello",name)  
    
    student = Student()  
    
    student.show("John")

    Python Parameterized Constructor

    The parameterized constructor has multiple parameters along with the self. Consider the following example.

    Example

    class Student:  
    
        # Constructor - parameterized  
    
        def __init__(self, name):  
    
            print("This is parametrized constructor")  
    
            self.name = name  
    
        def show(self):  
    
            print("Hello",self.name)  
    
    student = Student("John")  
    
    student.show()

    Output:

    This is parametrized constructor
    Hello John
    

    Python Default Constructor

    When we do not include the constructor in the class or forget to declare it, then that becomes the default constructor. It does not perform any task but initializes the objects. Consider the following example.

    Example

    class Student:  
    
        roll_num = 101  
    
        name = "Joseph"  
    
      
    
        def display(self):  
    
            print(self.roll_num,self.name)  
    
      
    
    st = Student()  
    
    st.display()

    Output:

    101 Joseph
    

    More than One Constructor in Single class

    Let’s have a look at another scenario, what happen if we declare the two same constructors in the class.

    Example

    class Student:  
    
        def __init__(self):  
    
            print("The First Constructor")  
    
        def __init__(self):  
    
            print("The second contructor")  
    
      
    
    st = Student()

    Output:

    The Second Constructor
    

    In the above code, the object st called the second constructor whereas both have the same configuration. The first method is not accessible by the st object. Internally, the object of the class will always call the last constructor if the class has multiple constructors.

    Note: The constructor overloading is not allowed in Python.

    Python built-in class functions

    The built-in functions defined in the class are described in the following table.

    SNFunctionDescription
    1getattr(obj,name,default)It is used to access the attribute of the object.
    2setattr(obj, name,value)It is used to set a particular value to the specific attribute of an object.
    3delattr(obj, name)It is used to delete a specific attribute.
    4hasattr(obj, name)It returns true if the object contains some specific attribute.

    Example

    class Student:  
    
        def __init__(self, name, id, age):  
    
            self.name = name  
    
            self.id = id  
    
            self.age = age  
    
      
    
        # creates the object of the class Student  
    
    s = Student("John", 101, 22)  
    
      
    
    # prints the attribute name of the object s  
    
    print(getattr(s, 'name'))  
    
      
    
    # reset the value of attribute age to 23  
    
    setattr(s, "age", 23)  
    
      
    
    # prints the modified value of age  
    
    print(getattr(s, 'age'))  
    
      
    
    # prints true if the student contains the attribute with name id  
    
      
    
    print(hasattr(s, 'id'))  
    
    # deletes the attribute age  
    
    delattr(s, 'age')  
    
      
    
    # this will give an error since the attribute age has been deleted  
    
    print(s.age)

    Output:

    John
    23
    True
    AttributeError: 'Student' object has no attribute 'age'
    

    Built-in class attributes

    Along with the other attributes, a Python class also contains some built-in class attributes which provide information about the class.

    The built-in class attributes are given in the below table.

    SNAttributeDescription
    1__dict__It provides the dictionary containing the information about the class namespace.
    2__doc__It contains a string which has the class documentation
    3__name__It is used to access the class name.
    4__module__It is used to access the module in which, this class is defined.
    5__bases__It contains a tuple including all base classes.

    Example

    class Student:    
    
        def __init__(self,name,id,age):    
    
            self.name = name;    
    
            self.id = id;    
    
            self.age = age    
    
        def display_details(self):    
    
            print("Name:%s, ID:%d, age:%d"%(self.name,self.id))    
    
    s = Student("John",101,22)    
    
    print(s.__doc__)    
    
    print(s.__dict__)    
    
    print(s.__module__)

    Output:

    None
    {'name': 'John', 'id': 101, 'age': 22}
    __main__
  • Python Classes and Objects

    In object-oriented programming, classes and objects play a vital role in programming. These are the two main pillars of OOP (Object-Oriented Programming). A class serves as a user-defined blueprint for creating objects, while objects are specific instances created based on that blueprint.

    Python Classes and Objects

    Class in Python

    A class is a group of objects that have common properties. It is a template or blueprint from which objects are created. It is a logical entity. It cannot be physical.

    A class is a user-defined instruction for creating objects. A class defines the data, characteristics, and behavior of the objects.

    Suppose a class is a prototype of a building. A building contains all the details about the floor, rooms, doors, windows, etc. We can make as many buildings as we want, based on these details. Hence, the building can be seen as a class, and we can create as many objects of this class.

    Syntax:

    The syntax of defining a class in Python is shown below:

    class ClassName:  
    
        # class attribute

    Example: Creating a Class

    Let us see an example to see how we create a Class in Python.

    # define a class  
    
    class Car:  
    
        car_type = "Sedan"  # Class attribute

    Explanation

    Here we see that we have defined a Class named Car. Inside the class, car_type = “Sedan” is a class attribute, which is shared among all instances of the class.

    Objects in Python

    An object is a real-world entity that has state and behaviour. It is an instance of a class. In other words, an object is a tangible thing that can be touched and felt, like a car or chair, etc. 

    Syntax:

    The syntax of defining an object in Python is shown below:

    NameofObject = ClassName()

    Example: Creating an Object

    Let us see an example to see how we create an Object in Python.

    Example

    class Car:  
    
        car_type = "Sedan"   
    
    # Create an object from the class  
    
    honda_city = Car()  
    
    # Access the class attribute  
    
    print(honda_city.car_type)

    Output:

    Sedan

    Explanation

    In the above example, we defined a Class named Car, and car_type = “Sedan” is a class attribute, which is shared among all instances of the class.

    Relation Between Class and Objects

    A Class, on its own, means nothing until it is used to create objects.

    A Class defines what attributes and behavior (methods) an object will have.

    Python Classes and Objects

    In this image, the Car is defined as a Class, and Car 1, Car 2, and Car 3 are the objects. For example, Car 1, Car 2, and Car 3 can be taken as Honda City, Hyundai Creta, etc.

    __init__() Function

    The __init__() function is a constructor method in Python that automatically initializes the object’s attributes when a new object is created from a class.

    Syntax:

    The syntax of using the __init__() function in Python is shown below:

    class ClassName:  
    
        def __init__(self, parameter1, parameter2, ...):  
    
            # Initializing instance variables  
    
            self.attribute1 = parameter1  
    
            self.attribute2 = parameter2  
    
           ……………and more

    Example: __init__() function

    Let us see an example to see how we use the __init__() function in Python.

    Example

    class Car:            # Class defined  
    
        car_type = "Sedan"  # Class attribute  
    
        def __init__(self, car_name, engine): #__init__()  function  
    
            self.car_name = car_name  # Instance attribute  
    
            self.engine = engine   # Instance attribute  
    
    car1 = Car("HondaCity", 250)  
    
    print(car1.car_name)   
    
    print(car1.engine)

    Output:

    HondaCity
    250
    

    Explanation

    In the above example, we have used __init__function to create parameters (car_name, engine) and assign them to instance attributes using self.

    self.car_name and self. Engine are instance attributes that store specific information related to each object.

    The class Car consists of an object named car1, and the object has:

    • car_name = “HondaCity”
    • engine = 250

    Self Parameter

    In Python, Self is a construct used within class methods to refer to the instance of the class. It enables us to access the methods and attributes of the object.

    Example: Self Parameter

    Let-s see an example of using a self parameter to access the methods and attributes of the object.

    Example

    class Car:  
    
        def __init__(self, car_name, engine):    
    
            self.car_name = car_name   
    
            self.engine = engine  
    
        def run(self):  #self parameter  
    
            print(f"{self.car_name} is running well")  
    
    # Creating an instance of Car  
    
    car1 = Car("HondaCity", 250)  
    
    car1.run()

    Output:

    Honda City is running well

    Explanation

    In the above example, we have used __init__function to create parameters (car_name, engine) and assign them to instance attributes using self.

    self.car_name and self.engine are instance attributes that store information, and the ‘self’ keyword allows each object to maintain its own data.

    We used the self as the parameter. The self.car_name allows this method to access the car-s name associated with that specific object.

    __str__ Method

    In Python, the __str__ method helps us to construct and define a custom string representation of an object.

    Example: __str__ method

    Let-s see an example where we are using the __str__ method.

    Example

    class Car:  
    
        def __init__(self, car_name, kms_driven):  
    
            self.car_name = car_name  
    
            self.kms_driven = kms_driven  
    
    #__str__  method  
    
        def __str__(self):    
    
            return f"{self.car_name} is {self.kms_driven} Kilometres driven."  #Returning a string  
    
            
    
    car1 = Car("Honda City", 45000)  
    
    car2 = Car("Swift Dzire", 15000)  
    
      
    
    print(car1)    
    
    print(car2)

    Output:

    Honda City has 45000 Kilometres driven.
    Swift Dzire has 15000 Kilometers driven.
    

    Explanation

    As we have learnt that the __str__ method helps us to construct and define a custom string representation of an object.

    The __str__ method is automatically called when we use print or str(object). The return statement defines what gets printed when we print the object.

    Class and Instance Variables

    The Variables defined in Python can be of two types:

    • Class Variables
    • Instance Variables

    These two variables are distinct from each other and are crucial to understand in order to learn about object-oriented programming.

    Class Variables

    Class variables are variables that are shared across all instances of a class. All objects of the class can access and modify them. Their value remains the same for every object unless explicitly overridden within a specific object.

    Instance Variables

    Instance variables in Python are unique to each instance or object of a class. The __init__ method is usually used to define Instance variables. The copy of instance variables is maintained by the objects themselves and independently of other objects.

    Example

    Let-s see an example to understand the Instance Variables more clearly:

    Example

    class Car:  
    
        # Class variable  
    
        car_type = "Sedan"  
    
        def __init__(self, carname, kms_driven):  
    
            # Instance variables  
    
            self.carname = carname  
    
            self.kms_driven = kms_driven  
    
    # Create objects  
    
    car1 = Car("HondaCity", 12000)  
    
    car2 = Car("BMW", 1500)  
    
    # Access class and instance variables  
    
    print(car1.car_type)  # (Class variable)  
    
    print(car1.carname)     # (Instance variable)  
    
    print(car2.carname)     # (Instance variable)  
    
    # Modify instance variables  
    
    car1.carname = "Alto"  
    
    print(car1.carname)     # (Updated instance variable)  
    
    # Modify class variable  
    
    Car.car_type = "Hatchback"  
    
    print(car1.car_type)  # (Updated class variable)  
    
    print(car2.car_type)

    Output:

    Sedan
    HondaCity
    BMW
    Alto
    Hatchback
    Hatchback
    

    Explanation

    In the above code, we have clearly explained Class variables and Instance variables.

    Here we have created the objects:

    car1 = Car("HondaCity", 12000)  
    
    car2 = Car("BMW", 1500)  

      And here we have accessed Class and Instance variables:

      print(car1.car_type)     # Output: Sedan  
      
      print(car1.carname)      # Output: HondaCity  
      
      print(car2.carname)      # Output: BMW

      We then modified the Class Variable and instance variable for all objects further.

      Benefits of Using Classes in Python

      • Avoiding Code Repetition: Classes help us define common functionality that can be reused throughout the code, thereby helping us avoid writing the same piece of code multiple times.
      • Keep Data and Behavior in a single entity: As we know, a Class determines the attributes and behavior of the objects, so we bundle all these functionalities together as it helps us to organize our code better.
      • Solve Complex Problems: Classes can help us solve complex and real-world programming problems, as they enable us to avoid code repetition and keep all data in a single entity.

      Conclusion

      Classes and Objects are important and closely related concepts in object-oriented programming. A class is a user-defined blueprint for creating objects, as it determines the attributes and behavior of each instance of that object.

      The object is an instance of that class. Imagine the Car as a Class, and the Honda City and the Tata Nexon as the objects.

    1. OOPs (Object-Oriented Programming) Concepts in Python

      In Python, Object Oriented Programming (OOP) is a programming paradigm that offers a way of structuring programs so that the properties and behaviours are bundled into individual objects. It allows developers to model real-world entities with the help of classes, acting as blueprints for objects.

      OOPs (Object-Oriented Programming) Concepts in Python

      Object Oriented Programming encourages modularity, scalability, and reusability, making the complex applications easier for management. Being OOP-friend language, Python enables efficient organization of code through object interactions. This approach enhances code maintainability by structuring it in a logical and hierarchical way. OOP is widely utilized in software development, game design, GUI applications, and data modeling, ensuring flexibility and efficiency in programming.

      The following are the main concepts of Object-Oriented Programming in Python:

      1. Classes and Objects
      2. Encapsulation
      3. Inheritance
      4. Abstraction
      5. Polymorphism

      Let us discuss these concepts with the help of examples.

      Classes and Objects in Python

      Class: A class is a blueprint for creating objects. It defines a set of attributes that characterize any object of the class. The attributes like data members (class and instance variables) and methods are accessed via dot notation.

      Object: An object is an instance of a class. It symbolizes a particular implementation of the class and holding its own data. For example, an object named car_one that belongs to a class Car is an instance of that class. A unique instance of a data structure that is defined by its class. An object comprises both data members (class and instance variables) and methods.

      OOPs (Object-Oriented Programming) Concepts in Python

      Python Classes and Objects Example

      Let us now understand the concept of classes and objects with the help of a simple example.

      Example

      # creating a class  
      
      class Employer:  
      
        def __init__(self, name, industry):  
      
          self.name = name          # attribute  
      
          self.industry = industry  # attribute  
      
        
      
        def display_info(self):     # method  
      
          print(f"Employer: {self.name} - {self.industry}")  
      
        
      
      # Creating an object  
      
      employer_one = Employer("Learn Python App, "Education")  
      
      employer_one.display_info()

      Output:

      Employer: Learn Python App - Education

      Explanation:

      In the above example, we have created a class called Employer. Within this class, we have created a constructor initializing two attributes as name and industry. We have then defined a method as display_info() in order to print the details of the Employer.

      We have then created an object of the Employer class passing the value to the name and industry and called the display_info() method to print the passed details of the Employer.

      Encapsulation in Python

      Encapsulation is one of the principles of Object-Oriented Programming (OOP) that refers to the bundling of attributes and methods inside a single class.

      It restricts the direct access to data and allows controlled modification through methods. This also helps to accomplish data hiding.

      In Python, we can achieve encapsulation with the help of private attributes (prefixing variables with __).

      Python Encapsulation Example

      Let us now look at a simple example showing how encapsulation works in Python.

      Example

      # creating a class  
      
      class Employer:  
      
          def __init__(self, employee_count):  
      
              self.__employee_count = employee_count  # Private attribute (name mangling)  
      
        
      
          def get_employee_count(self): # method  
      
              return self.__employee_count  # Accessing private attribute via method  
      
        
      
      # creating an object  
      
      employer_one = Employer(1500)  
      
        
      
      print("No. of Employees:", employer_one.get_employee_count())  
      
      # print("No. of Employees:", employer_one.__employee_count) # Trying to access private attribute directly (returns Error)  
      
        
      
      # Name Mangling  
      
      print("No. of Employees (Accessing via Name Mangling):", employer_one._Employer__employee_count) # Accessing via name mangling

      Output:

      No. of Employees: 1500
      No. of Employees (Accessing via Name Mangling): 1500
      

      Explanation:

      In the above example, we have created the Employer class having a private attribute as __employee_count. We have then defined a method to return the employee count. We then created an object of the Employer class and called the class method to print the employee count. We also tried directly accessing the private attribute which returns an error. Therefore, we used the name mangling to access the attribute.

      To learn more about Encapsulation, visit: Encapsulation in Python – Learn Python App

      Inheritance in Python

      Inheritance is another principle of Object-Oriented Programming (OOP) that allows a class (child class) to reuse the properties and methods from another class (parent class). It supports hierarchical classification and promotes reusability of code.

      OOPs (Object-Oriented Programming) Concepts in Python

      Python Inheritance Example

      Let us take a look at a simple example showing how inheritance work in Python.

      Example

      # creating a class (parent class)  
      
      class Vertebrate:  
      
          def __init__(self, name):  
      
              self.name = name  
      
              self.spine = True  
      
        
      
          def move(self):  
      
              print(f"{self.name} is moving.")  
      
        
      
      # creating another class (child class)  
      
      class Mammal(Vertebrate):  
      
          def __init__(self, name, fur_color):  
      
              super().__init__(name)  
      
              self.fur_color = fur_color  
      
        
      
          def nurse(self):  
      
              print(f"{self.name} is nursing its young.")  
      
        
      
      # creating another class (child class)  
      
      class Bird(Vertebrate):  
      
          def __init__(self, name, wingspan):  
      
              super().__init__(name)  
      
              self.wingspan = wingspan  
      
          def fly(self):  
      
              print(f"{self.name} is soaring through the air.")  
      
        
      
      # creating an object of Mammal class  
      
      dog = Mammal("Bear", "brown")  
      
      dog.move()  
      
      dog.nurse()  
      
      print("Spine Exist?:", dog.spine)  
      
        
      
      # creating an object of Bird class  
      
      eagle = Bird("Eagle", 2)  
      
      eagle.move()  
      
      eagle.fly()  
      
      print("Spine Exist?:", eagle.spine)

      Output:

      Bear is moving.
      Bear is nursing its young.
      Spine Exist?: True
      Eagle is moving.
      Eagle is soaring through the air.
      Spine Exist?: True
      

      Explanation:

      In the above example, we have created a base class as Vertebrate with public attributes as name, and spine (set to True). We have also defined a method as move(). We then created two child classes as Mammal, and Bird that inherits the properties and methods from the Vertebrate class. We then defined different methods of the child class.

      At last, we have created the objects of the child classes and tried accessing the different attributes and methods. As a result, both objects can easily access the attributes and methods of the child classes as well as the parent class.

      To learn more about Inheritance, visit: Inheritance in Python – Learn Python App

      Abstraction

      Abstraction is another principle of object-oriented programming (OOP), that allows us to hide the internal implementation details while showing only the necessary functionality. The main goal of abstraction is to enable our focus on “what to do” rather than “how to do it”.

      Python Abstraction Example

      Let us take a look at a simple example showing how abstraction work in Python.

      Example

      # importing the ABC class and abstractmethod decorator from abc module  
      
      from abc import ABC, abstractmethod  
      
        
      
      # creating a class  
      
      class Three_Dimensional_Shapes(ABC):  
      
        @abstractmethod  
      
        def calc_volume(self):  
      
          pass  
      
        
      
      # creating another class to implement abstraction  
      
      class Cube(Three_Dimensional_Shapes):  
      
        def __init__(self, side_length):  
      
          self.side_length = side_length  
      
        
      
        def calc_volume(self):  
      
          return self.side_length ** 3  
      
        
      
      # creating an object of Cube class  
      
      side_length = 6  
      
      cube = Cube(side_length)  
      
        
      
      # printing the results  
      
      print("Cube's Side:", side_length)  
      
      print("Cube's Volume:", cube.calc_volume())

      Output:

      Cube's Side: 6
      Cube's Volume: 216
      

      Explanation:

      In the above example, we have imported the ABC class and abstractmethod decorator from the abc module. We have then defined a class that inherits the properties of the ABC class and defined an abstract method inside it. We have then defined another class that inherits the properties from the base class and overridden the method to return the preferred output.

      To learn more about Abstraction, visit: Abstraction in Python –

      Learn python app

      Polymorphism in Python

      Polymorphism is another significant principle of Object-Oriented Programming (OOP) that simply means more than one form. Polymorphism allows methods to have the same name; however, their behaviour is different on the basis of the context of the object. We can achieve Polymorphism through method overriding or overloading.

      OOPs (Object-Oriented Programming) Concepts in Python

      Python Polymorphism Example

      Let us take a look at the following example showing how polymorphism works in Python.

      Example

      # creating a base class  
      
      class Vertebrate:  
      
          def __init__(self, name):  
      
              self.name = name  
      
        
      
          def move(self):  
      
              print(f"{self.name} moves in a generic way.")  
      
        
      
      # creating a child class  
      
      class Mammal(Vertebrate):  
      
          def move(self):   # method overriding  
      
              print(f"{self.name} walks.")  
      
        
      
      # creating a child class  
      
      class Bird(Vertebrate):  
      
          def move(self):   # method overriding  
      
              print(f"{self.name} flies.")  
      
        
      
      # creating object as a list  
      
      animals = [Vertebrate("Generic Vertebrate"), Mammal("Dog"), Bird("Eagle")]  
      
        
      
      for animal in animals:  
      
          animal.move()

      Output:

      Generic Vertebrate moves in a generic way.
      Dog walks.
      Eagle flies.
      

      Explanation:

      In the above example, we have created a base class as Vertebrate with a public attribute as name. We have also defined a method as move(). We then created two child classes as Mammal, and Bird that inherits the properties and methods from the Vertebrate class. Here, we have overridden the move() method for both the classes to print the desire output.

      To learn more about Polymorphism, visit: Polymorphism in Python – Tpoint Tech

      Advantages of OOPs over Procedural Programming

      The Object-oriented Programming (OOP) has various advantages over the procedure-oriented programming. Some of them are discussed below:

      1. OOP makes development and maintenance easier; whereas in procedural programming, managing code becomes difficult as the project size grows.
      2. OOP enables data hiding; whereas in procedural programming, global data can be accessed from anywhere.
      3. OOP provides the ability to simulate real-world events much more effectively. We can provide the solution of real word problems using OOP.

      Conclusion

      In this tutorial, we learned that Object-Oriented Programming (OOP) in Python is a powerful paradigm that helps organize and structure code using real-world concepts like classes and objects. It promotes modularity, code reusability, and better data security through key principles such as encapsulation, abstraction, inheritance, and polymorphism. By modeling programs as interacting objects, OOP makes code easier to maintain, scale, and extend (especially in case of large and complex applications)