Object Oriented Programming (OOP) with examples in Python

Utpal Kumar   10 minute read      

This post explains the concepts of object-oriented programming (OOP) and its key features, including encapsulation, abstraction, inheritance, and polymorphism. It provides examples of how these concepts can be implemented using Python, such as using private and protected variables, defining abstract classes and interfaces, creating subclasses, and implementing method overriding.

Introduction

Object-oriented programming (OOP) is a programming paradigm that organizes code into objects, which are instances of classes that contain data (attributes) and behavior (methods). OOP is based on the concept of “objects,” which represent real-world entities or concepts.

In OOP, a class is a blueprint or template for creating objects. A class defines the properties and methods that an object will have, and each object created from a class will have its own set of data and behavior. OOP is a powerful and flexible programming paradigm that allows developers to create complex, modular, and scalable applications.

Many programming languages support object-oriented programming (OOP) concepts. Some of the most popular ones are:

  1. Java: Java is a popular OOP language that was designed to be simple, secure, and platform-independent.
  2. Python: Python is another popular OOP language that is known for its simplicity and ease of use.
  3. C++: C++ is an OOP language that is often used for building large-scale applications and systems. Other languages are Swift, JavaScript, Ruby, PHP, etc.

Key Features

Some key features of OOP include:

  1. Encapsulation: This is the process of hiding implementation details and exposing only the necessary information or functionality to the user. Encapsulation is achieved by defining public and private methods and variables within a class.
  2. Abstraction: This is the process of simplifying complex systems by breaking them down into smaller, more manageable parts. In OOP, abstraction is achieved by creating abstract classes or interfaces that define common functionality or behavior, which can then be implemented by other classes.
  3. Inheritance: This is the process of creating a new class from an existing class, inheriting its properties and methods. The new class can add or modify functionality as needed, while still maintaining the original properties and methods of the parent class.
  4. Polymorphism: This is the ability of objects to take on many forms or behaviors. In OOP, polymorphism is achieved through method overriding and method overloading, which allow methods to have different implementations depending on the specific object or context.

Encapsulation

Encapsulation is the process of hiding implementation details and exposing only the necessary information or functionality to the user. In Python, encapsulation can be achieved using the following methods:

  1. Private variables and methods: In Python, private variables and methods can be defined by using the double underscore (__) prefix. This makes the variable or method inaccessible outside the class. For example:
class MyClass:
    def __init__(self):
        self.__my_var = 10

    def __my_method(self):
        print("This is a private method")

my_obj = MyClass()
print(my_obj.__my_var) # This will raise an AttributeError
my_obj.__my_method() # This will also raise an AttributeError
  1. Protected variables and methods: In Python, protected variables and methods can be defined by using the single underscore (_) prefix. This makes the variable or method accessible only within the class and its subclasses. For example:
class MyClass:
    def __init__(self):
        self._my_var = 10

    def _my_method(self):
        print("This is a protected method")

class MySubclass(MyClass):
    def __init__(self):
        super().__init__()

    def print_var(self):
        print(self._my_var) # This is accessible from the subclass

my_obj = MyClass()
print(my_obj._my_var) # This is accessible from outside the class, but it's not recommended
my_obj._my_method() # This is accessible from outside the class, but it's not recommended

my_subobj = MySubclass()
my_subobj.print_var() # This will print 10
  1. Property methods: In Python, property methods can be used to control access to variables and provide validation or other custom behavior. Property methods can be defined using the @property decorator and the setter method. The @property decorator is used to define a method as a “getter” for a class attribute. When this method is called, it returns the value of the attribute. For example:
class MyClass:
    def __init__(self):
        self._my_var = 10

    @property
    def my_var(self):
        return self._my_var

    @my_var.setter
    def my_var(self, value):
        if value > 0:
            self._my_var = value

my_obj = MyClass()
print(my_obj.my_var) # This will print 10
my_obj.my_var = 20
print(my_obj.my_var) # This will print 20
my_obj.my_var = -5 # This will not change the value of my_var because of the validation defined in the setter method
print(my_obj.my_var) # This will still print 20

In Python, a property is a special kind of method that allows you to control access to an object’s attributes. Property methods are useful because they allow you to add custom logic to the act of setting or retrieving an object’s attributes, without requiring users of your object to change their code. Let us see another example:

class MyClass:
    def __init__(self):
        self._x = None

    @property
    def x(self):
        print("Getting x")
        return self._x

    @x.setter
    def x(self, value):
        print("Setting x")
        self._x = value

    @x.deleter
    def x(self):
        print("Deleting x")
        del self._x

obj = MyClass()
obj.x = 10  # This calls the setter method
print(obj.x)  # This calls the getter method

# Delete the value of x
del obj.x
# Output: Deleting x

# Try to get the value of x again
print(obj.x)
# Output: Getting x
# Output: None

In this example, x is a property of the MyClass object. The @property decorator is used to define a method called x that serves as the getter for the x property. This method is called whenever the x property is accessed. In addition, a method called x.setter is defined to set the value of the x property. When the x property is set, this method is called.

The @property decorator can be used without the setter method, in which case the property becomes read-only. In addition, it can be used with the deleter method, which allows you to define what happens when the property is deleted. Deleter methods can be useful when you need to perform additional actions when a property is deleted, such as logging, cleaning up resources, or invalidating related properties.

In short, encapsulation in Python is achieved through the use of private and protected variables and methods, as well as property methods to control access to variables and provide validation or other custom behavior.

Abstraction

Abstraction is a key concept in object-oriented programming that allows complex systems to be designed and implemented in a more manageable way. Abstraction involves creating a simplified, abstract representation of a complex system that hides its implementation details and exposes only the essential features that are needed for a particular purpose.

In Python, abstraction is achieved through the use of abstract classes and interfaces. An abstract class is a class that cannot be instantiated directly, but can only be used as a superclass for other classes. An abstract class may define one or more abstract methods, which are methods that are declared but not implemented. Abstract methods serve as placeholders for methods that will be implemented in subclasses, and they provide a way to ensure that subclasses provide their own implementation of the method.

Here’s an example of how abstraction can be achieved in Python using an abstract class:

class Car:
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

    def start(self):
        raise NotImplementedError("Subclass must implement abstract method")

    def stop(self):
        raise NotImplementedError("Subclass must implement abstract method")

In this example, we define a Car class that has three attributes: make, model, and year. We also define two methods, start and stop, which raise a NotImplementedError exception. These methods are considered “abstract” because they don’t have any implementation details yet.

Now, let’s create a subclass of Car that implements the abstract methods:

class ElectricCar(Car):
    def __init__(self, make, model, year, battery_size):
        super().__init__(make, model, year)
        self.battery_size = battery_size

    def start(self):
        print("Starting the electric car")

    def stop(self):
        print("Stopping the electric car")


my_car = ElectricCar("Tesla", "Model S", 2021, 100)
my_car.start()  # Output: Starting the electric car
my_car.stop()   # Output: Stopping the electric car

In this example, we create a subclass of Car called ElectricCar. We define the same three attributes as in the parent class, and we also add a battery_size attribute. We then implement the abstract start and stop methods with some implementation details specific to an electric car.

Here, we create an instance of ElectricCar and call its start and stop methods. We don’t need to know the details of how these methods are implemented, as they are abstracted away by the Car class.

Another way to perform abstraction in Python is to use the “Abstract Base Classes” module. It is a built-in module that provides a way to define abstract classes and abstract methods in Python.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius * self.radius

# Create instances of Rectangle and Circle
rect = Rectangle(5, 6)
circ = Circle(3)

# Call the area() method on each instance
print(rect.area())  # Output: 30
print(circ.area())  # Output: 28.26

In this example, we define an abstract class Shape that has an abstract method area. We then define two concrete subclasses of Shape, Rectangle and Circle, that implement the area method with their own implementation details.

The abc library provides a way to enforce the use of abstract classes and methods in the code. When we define an abstract method using the @abstractmethod decorator, any concrete subclass that fails to implement that method will raise a TypeError exception when an attempt is made to instantiate the subclass. This ensures that all concrete subclasses of an abstract class provide an implementation for the abstract method.

Inheritance

Inheritance is a feature of object-oriented programming that allows a new class to be based on an existing class, inheriting all its properties and methods. The existing class is known as the parent class or superclass, and the new class is known as the child class or subclass.

In Python, inheritance is achieved by creating a subclass that inherits the properties and methods of the superclass. The syntax for creating a subclass is as follows:

class Subclass(Superclass):
    pass

This creates a new class Subclass that inherits from the Superclass. The pass keyword is used here to indicate that the subclass has no additional properties or methods.

The subclass can then override or extend the properties and methods of the superclass, or add new ones. For example:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print("I am an animal")

class Cat(Animal):
    def __init__(self, name, color):
        super().__init__(name)
        self.color = color

    def speak(self):
        print("Meow")

    def catch_mouse(self):
        print("I caught a mouse!")

my_animal = Animal("Leo")
my_animal.speak() # This will print "I am an animal"

my_cat = Cat("Whiskers", "gray")
my_cat.speak() # This will print "Meow"
my_cat.catch_mouse() # This will print "I caught a mouse!"
print(my_cat.name) # This will print "Whiskers"
print(my_cat.color) # This will print "gray"

In this example, Cat is a subclass of Animal, and it overrides the speak method to print “Meow” instead of “I am an animal”. It also adds a new method catch_mouse that is specific to cats. The __init__ method of Cat calls the __init__ method of Animal using the super() function to initialize the name property inherited from Animal.

In short, inheritance in Python allows for code reusability and makes it easier to create new classes that share properties and methods with existing ones.

Polymorphism

Polymorphism is a feature of object-oriented programming that allows objects of different classes to be treated as if they are of the same class. This means that you can use a single interface to represent multiple classes, and you can write code that can work with objects of different classes without needing to know their specific types.

In Python, polymorphism is achieved through the use of duck typing and method overriding. Duck typing means that if an object walks like a duck and quacks like a duck, then it is treated as a duck. This means that if two objects have the same methods or attributes, they can be treated as if they are the same type of object.

Method overriding is the ability of a subclass to provide its own implementation of a method that is already defined in its parent class. When a method is called on an object of the subclass, the implementation in the subclass is used instead of the one in the parent class.

Here’s an example of how polymorphism can be achieved in Python:

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

def animal_speak(animal):
    print(animal.speak())

my_dog = Dog("Fido")
my_cat = Cat("Whiskers")

animal_speak(my_dog) # This will print "Woof!"
animal_speak(my_cat) # This will print "Meow!"

In this example, Animal is a superclass that defines a method speak, which does nothing. Dog and Cat are subclasses that inherit from Animal and override the speak method to return “Woof!” and “Meow!”, respectively.

The animal_speak function takes an object of type Animal as an argument, and calls its speak method. Since Dog and Cat are both subclasses of Animal and have overridden the speak method, they can be passed as arguments to animal_speak, and their speak method will be called accordingly.

In short, polymorphism in Python allows for code that is more flexible, reusable, and easier to maintain, since it can work with objects of different types without needing to know their specific types.

Disclaimer of liability

The information provided by the Earth Inversion is made available for educational purposes only.

Whilst we endeavor to keep the information up-to-date and correct. Earth Inversion makes no representations or warranties of any kind, express or implied about the completeness, accuracy, reliability, suitability or availability with respect to the website or the information, products, services or related graphics content on the website for any purpose.

UNDER NO CIRCUMSTANCE SHALL WE HAVE ANY LIABILITY TO YOU FOR ANY LOSS OR DAMAGE OF ANY KIND INCURRED AS A RESULT OF THE USE OF THE SITE OR RELIANCE ON ANY INFORMATION PROVIDED ON THE SITE. ANY RELIANCE YOU PLACED ON SUCH MATERIAL IS THEREFORE STRICTLY AT YOUR OWN RISK.


Leave a comment