OOPs Fundamentals

OOPS and Its Importance

Object-Oriented Programming (OOPs) is a paradigm that revolves around the concept of objects, encapsulating data and behaviour. In Python, OOPs is a fundamental approach to structuring code, promoting modularity, and enhancing code reuse. Understanding OOPs is crucial for building scalable, maintainable, efficient software solutions.

The idea of “objects” that contain data and associated functions is at the core of it. OOPs encourages modularity and reusability by promoting encapsulation, abstraction, inheritance, and polymorphism. OOPs improves code structure by emphasising the creation of classes and hierarchies, which makes it simpler to comprehend, scale, and maintain. OOPs concepts enhance the flexibility, adaptability, and general software quality of projects of all sizes.

Class, Instance/Object, and the __init__ Method

  1. Class: A class is an object creation blueprint. It specifies a collection of properties and functions that define every object that is instantiated from that class.
  2. Instance/Object: An instance of a class is called an object. It stands for a particular implementation of the class, complete with special characteristics and behaviours.
  3. init Method: When an object is formed, the constructor, commonly known as the ‘__init__’ method, is called. It sets the initial values for the attributes of the object.

Let’s create a simple example in Python to illustrate the concepts of ‘class’, ‘instance (object)’ , and the ‘__init__’ method.

python

# Define a simple class
class Dog:
    # Class variable
   
species = “Shih Tzu”

   
# __init__ method (constructor)
   
def __init__(self, name, age):
        # Instance variables
       
self.name = name
        self.age = age

# Create an instance (object) of the class
my_dog = Dog(name=“Buddy”, age=3)

# Accessing attributes
print(f”{my_dog.name} is a {my_dog.species} and is {my_dog.age} years old.”)

In this instance:

  • We define the ‘Dog’
  • A common class variable, called ‘species’, exists inside the class and is used by all instances.
  • The ‘__init__’ method is the constructor, responsible for initializing the instance variables ‘name and age’ when an object is created.
  • We create an instance of the ‘Dog’ class called ‘my_dog’ and pass values for the ‘name and age’ attributes during object creation.
  • We access the attributes of the object using dot notation name, my_dog.species, and my_dog.age and print a statement to display the information.

When you run this program, you’ll get output like:

python

Buddy is a Shih Tzu and is 3 years old.

Creating Classes and Objects

Let’s take a hands-on approach to creating classes and instantiating objects. We’ll cover the essential syntax and conventions, demonstrating how to structure your code for better readability and maintainability.

python

# Define a simple class
class Car:
    # Class variable
   
car_count = 0

    # __init__ method (constructor)
   
def __init__(self, make, model, year):
        # Instance variables
       
self.make = make
        self.model = model
        self.year = year

        # Increment the class variable (car_count) each time an object is created
       
Car.car_count += 1

    # Instance method
   
def display_info(self):
        print(f”{self.year} {self.make} {self.model}”)

# Create instances (objects) of the class
car1 = Car(“Toyota”, “Camry”, 2022)
car2 = Car(“Honda”, “Accord”, 2021)

# Accessing attributes and calling methods
print(f”Car Count: {Car.car_count}”)

car1.display_info()
car2.display_info()

In this example:

  • We define a class named Car.
  • Inside the class, we have a class variable (car_count) to keep track of the number of cars created.
  • The __init__ method initializes the instance variables (make, model, year) when an object is created. It also increments the class variable (car_count) to keep track of the number of cars.
  • We define an instance method (display_info) to display information about the car.
  • We create two instances (car1 and car2) of the Car class by calling the class as if it were a function, passing the necessary arguments.
  • We access the attributes (make, model, year) and call the method (display_info) on each object.

When you run this program, you’ll get output like:

python

Car Count: 2

2022 Toyota Camry

2021 Honda Accord

Accessing Attributes and Calling Methods

Within the domain of object-oriented programming (OOPs), attributes symbolize the traits or features inherent to an object, while methods denote functions connected to the object that specify its actions. This paradigm affords developers the ability to construct a structured and modular representation of real-world entities and their interrelationships. Top of Form

a. Accessing Attributes – Accessing attributes involves retrieving the values stored within an object. In our Car example, we can create an instance of the class and access its attributes:

python

my_car = Car(make=“Toyota”, model=“Camry”, year=2022)
print(“Make:”, my_car.make)
print(“Model:”, my_car.model)
print(“Year:”, my_car.year)

Here, we access the attributes of ‘my_car’ using dot notation (my_car.make, my_car.model, my_car.year).

b. Calling Methods – Methods define the behaviour of an object. In our Car class, calling the display_info method returns a formatted string with information about the car:

python

car_info = my_car.display_info()
print(“Car Information:”, car_info)

Here, we call the ‘display_info’ method using dot notation (my_car.display_info()).

Real-world Application

Let’s consider a real-world example using Python and a simple class to represent a car.

python

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

    def
start_engine(self):
        if not self.is_engine_running:
            print(f”{self.year} {self.make} {self.model}’s engine is starting.”)
            self.is_engine_running = True
        else
:
            print(“The engine is already running.”)

    def stop_engine(self):
        if self.is_engine_running:
            print(f”{self.year} {self.make} {self.model}’s engine is stopping.”)
            self.is_engine_running = False
        else
:
            print(“The engine is already stopped.”)

    def display_info(self):
        print(f”{self.year} {self.make} {self.model}, Engine Running: {self.is_engine_running}”)

# Creating an instance of the Car class
my_car = Car(“Toyota”, “Camry”, 2022)

# Accessing attributes
print(f”Car Information: {my_car.year} {my_car.make} {my_car.model}”)

# Calling methods
my_car.start_engine()
my_car.display_info()
my_car.stop_engine()
my_car.display_info()

In this example:

  • Attributes: make, model, year, and is_engine_running are attributes of the Car class, representing the car’s make, model, manufacturing year, and the status of the engine.
  • Methods: start_engine(), stop_engine(), and display_info() are methods of the Car class. start_engine() and stop_engine() modify the is_engine_running attribute, simulating the starting and stopping of the car’s engine. display_info() prints information about the car, including whether the engine is running.
  • Accessing attributes: You can access the attributes of an instance of the Car class using dot notation, such as my_car.year or my_car.make.
  • Calling methods: You can call methods on an instance of the Car class using dot notation, such as my_car.start_engine() or my_car.display_info().

Variable Types

In OOPs, variables play a crucial role. We’ll explore the different variable types in Python classes, including instance variables, class variables, and local variables. Best practices for variable usage will also be discussed.

  1. Class Variables: Class variables are shared among all instances of a class. They are defined outside of any method and are accessed using the class name.
  2. Instance Variables: Instance variables are unique to each instance of a class. They are defined within the __init__ method and are prefixed with self.

Let’s create an example using the Dog class with both class and instance variables:

python

class Dog:
    # Class variable
   
species = “Canis familiaris”

   
def __init__(self, name, age):
        # Instance variables
       
self.name = name
        self.age = age

# Create instances of the Dog class
dog1 = Dog(“Buddy”, 3)
dog2 = Dog(“Max”, 2)

# Accessing instance variables
print(f”{dog1.name} is {dog1.age} years old.”)
print(f”{dog2.name} is {dog2.age} years old.”)

# Accessing the class variable
print(f”All dogs belong to the species {Dog.species}.”)

# Modifying instance variable
dog1.age += 1

# Displaying updaed information
print(f”{dog1.name} is now {dog1.age} years old.”)

In this illustrative scenario:

  • The Dog class features a class variable named species established with the value “Canis familiaris.”
  • The __init__ method takes action upon the creation of a Dog object by initializing individual attributes such as name and age.
  • Two distinct instances of the Dog class, denoted as dog1 and dog2, come into existence, each characterized by specific values assigned to their name and age attributes.
  • To retrieve information, instance variables are accessed through the use of dot notation, for instance, dog1.name and dog1.age.
  • Meanwhile, the class variable species is accessed by referring to the class name directly, i.e., Dog.species.

Method Types

a. Instance Methods: Instance methods, nestled within a class, act on particular instances by accessing and adjusting their attributes. These methods articulate behaviours tailored to individual objects in the realm of object-oriented programming.

python

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

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

b. Class Methods: Within a class, class methods function on the class itself, not specific instances. Identified by the @classmethod decorator, they take the class as their initial parameter, offering a distinct way to manipulate class-level attributes.

python

class Person:

    count = 0  # Class variable to count the number of instances

    def __init__(self, name):

        self.name = name

        Person.count += 1  # Incrementing count for each instance creation

    @classmethod

    def display_count(cls):

        return f”Total number of persons: {cls.count}”

# Creating instances of the Person class

person1 = Person(“Anay”)

person2 = Person(“Anita”)

# Accessing class method using the class name

print(Person.display_count())  # Output: Total number of persons: 2

In this example, the Person class has a class variable count to track the number of instances created. The display_count class method accesses and returns the count. It doesn’t operate on instance-specific data but on the class itself. The class method is invoked using the class name.

c. Static Methods: Static methods, residing within a class, operate independently of instances or class attributes. Employing the @staticmethod decorator, they offer autonomous utility within the class structure.

python

class MathOperations:

    @staticmethod

    def add(a, b):

        return a + b

    @staticmethod

    def subtract(a, b):

        return a – b

# Calling static methods without creating an instance

print(MathOperations.add(5, 3))       # Output: 8

print(MathOperations.subtract(10, 4)) # Output: 6

In this example, the MathOperations class contains static methods (add and subtract). These methods don’t require an instance of the class to be created. They perform basic mathematical operations and are called directly using the class name.

Conclusion

Python’s Object-Oriented Programming (OOPs) serves as a solid framework for structuring code. A profound grasp of classes, objects, attributes, and methods is crucial for crafting code that is efficient, scalable, and easily understandable. Mastering OOPs principles empowers developers to design software mirroring real-world entities, nurturing an intuitive and easily manageable codebase. Proficiency in applying OOPs not only streamlines code organization but also elevates the overall clarity and maintainability of programs. Through the application of these principles, developers unlock the potential to create software systems that closely emulate and address the intricacies of the real world, contributing to the development of resilient and adaptable applications.

Nikhil Bhatia

Talent Transformation Specialist

Leave a Comment

Your email address will not be published.