Understanding OOPs in Python 🐍🌠
Tags: python, oops, beginners, datascience
Published: 2024-06-24 01:45:43



#### What is OOP?

Object-Oriented Programming (OOP) is a programming paradigm that uses "objects" to design applications and computer programs. It utilizes several principles such as inheritance, encapsulation, polymorphism, and abstraction to create modular and reusable code.

#### Key Principles of OOP

1. **Encapsulation**
   
2. **Inheritance**
   
3. **Polymorphism**

4. **Abstraction**

---

### Basic Definitions of Class and Object

**Class:**
A class in programming is a blueprint or prototype that defines the attributes (data) and methods (functions) that the objects created from the class will have. It is a way to encapsulate data and functionality together. In simpler terms, a class is like a template for creating objects.

**Object:**
An object is an instance of a class. It is a specific implementation of the class template, containing real values instead of variables. Objects are used to interact with the data and functions defined in the class.




![description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mixqym637ba2y0rv85i8.png)

In the image, we see a representation of a class and an object using the concept of a Pokémon, specifically Pikachu.

- **Class**: The top part of the image shows a class named "Pokemon." This class defines the structure that all Pokémon objects will follow.
  - **Attributes**: These are the properties or data stored in the class. For the "Pokemon" class, the attributes are:
    - `Name`: Pikachu
    - `Type`: Electric
    - `Health`: 70
  - **Methods**: These are the functions or behaviors that objects created from the class can perform. The methods in the "Pokemon" class are:
    - `attack()`
    - `dodge()`
    - `evolve()`

- **Object**: On the left side of the image, we have Pikachu, which is an instance of the "Pokemon" class. Pikachu has specific values for the attributes defined in the class:
  - `Name`: Pikachu
  - `Type`: Electric
  - `Health`: 70

This image illustrates the relationship between a class (the blueprint) and an object (the instance). Pikachu, the object, is created based on the "Pokemon" class, inheriting its structure and functionalities.






---

### Encapsulation

**Definition:**
Encapsulation is the practice of wrapping the data (attributes) and the methods (functions) that operate on the data into a single unit, or class. It also involves restricting direct access to some of the object's components, which can help prevent accidental interference and misuse of the data.

**Daily Life Example:**
Consider a coffee machine. You interact with it by pressing buttons to make coffee, fill the water tank, etc. You don't need to know the internal mechanics of how it heats the water or brews the coffee. The machine encapsulates all the details.

**Python Code Example:**

```python
class CoffeeMachine:
    def __init__(self):
        self._water_level = 0  # The underscore indicates this attribute is intended to be private.
    
    def fill_water_tank(self, amount):
        self._water_level += amount
        print(f"Water tank filled. Current level: {self._water_level} ml")
    
    def make_coffee(self):
        if self._water_level > 0:
            print("Coffee made!")
            self._water_level -= 50  # assume each coffee uses 50ml
        else:
            print("Please fill the water tank.")
    
    def get_water_level(self):
        return self._water_level

# Using the CoffeeMachine class
machine = CoffeeMachine()
machine.fill_water_tank(100)
machine.make_coffee()
machine.make_coffee()
print(f"Remaining water level: {machine.get_water_level()} ml")
```

**Explanation:**

- **Class Definition**: We define a class `CoffeeMachine`.
- **Attributes**: `self._water_level` is an attribute that represents the water level in the machine. The underscore (`_`) before `water_level` is a convention indicating that this attribute is intended to be private.
- **Methods**: 
  - `fill_water_tank(amount)`: Adds the specified amount of water to the tank.
  - `make_coffee()`: Makes coffee if there is enough water.
  - `get_water_level()`: Returns the current water level.
- **Encapsulation**: The internal state (`_water_level`) is not directly accessible from outside the class, enforcing encapsulation.

### Inheritance

**Definition:**
Inheritance is a mechanism where a new class inherits attributes and methods from an existing class. The new class is called the derived (or child) class, and the existing class is the base (or parent) class.

**Daily Life Example:**
Consider a basic phone and a smartphone. A basic phone can make calls. A smartphone can make calls too, but it can also install apps, browse the internet, etc. The smartphone class can inherit the properties of the basic phone class and add additional features.

**Python Code Example:**

```python
class Phone:
    def __init__(self, brand, model):
        self.brand = brand
        self.model = model
    
    def make_call(self, number):
        print(f"Calling {number} from {self.brand} {self.model}")

class Smartphone(Phone):
    def __init__(self, brand, model, os):
        super().__init__(brand, model)  # Call the constructor of the parent class
        self.os = os
    
    def install_app(self, app_name):
        print(f"Installing {app_name} on {self.brand} {self.model}")

# Using the Phone and Smartphone classes
basic_phone = Phone("Nokia", "3310")
smart_phone = Smartphone("Apple", "iPhone 14", "iOS")

basic_phone.make_call("123456789")
smart_phone.make_call("987654321")
smart_phone.install_app("WhatsApp")
```

**Explanation:**

- **Base Class (Phone)**:
  - Attributes: `brand` and `model`.
  - Method: `make_call(number)` which prints a calling message.
- **Derived Class (Smartphone)**:
  - Inherits from `Phone`.
  - Adds a new attribute `os`.
  - Adds a new method `install_app(app_name)`.
  - Uses `super()` to call the constructor of the `Phone` class.

### Polymorphism

**Definition:**
Polymorphism allows for methods to do different things based on the object it is acting upon, even though they share the same name.

**Daily Life Example:**
Consider a remote control that can work with multiple devices like a TV, DVD player, and sound system. You use the same `power_on` button to turn all these devices on, but the internal implementation for each device is different.

**Python Code Example:**

```python
class TV:
    def power_on(self):
        print("The TV is now ON")

class DVDPlayer:
    def power_on(self):
        print("The DVD Player is now ON")

class SoundSystem:
    def power_on(self):
        print("The Sound System is now ON")

# Function that uses polymorphism
def turn_on_device(device):
    device.power_on()

# Using the function with different objects
tv = TV()
dvd_player = DVDPlayer()
sound_system = SoundSystem()

turn_on_device(tv)
turn_on_device(dvd_player)
turn_on_device(sound_system)
```

**Explanation:**

- **Classes (TV, DVDPlayer, SoundSystem)**: Each class has a `power_on()` method.
- **Function (turn_on_device)**: This function takes any device and calls its `power_on()` method.
- **Polymorphism**: The same `turn_on_device()` function works with different types of devices.

### Abstraction

**Definition:**
Abstraction is the concept of hiding the complex implementation details and showing only the essential features of the object.

**Daily Life Example:**
When you drive a car, you use the steering wheel, pedals, and gear shift. You don't need to understand how the engine or transmission system works to drive the car.

**Python Code Example:**

```python
from abc import ABC, abstractmethod

class Vehicle(ABC):
    @abstractmethod
    def start_engine(self):
        pass
    
    @abstractmethod
    def stop_engine(self):
        pass

class Car(Vehicle):
    def start_engine(self):
        print("Car engine started")
    
    def stop_engine(self):
        print("Car engine stopped")

class Motorcycle(Vehicle):
    def start_engine(self):
        print("Motorcycle engine started")
    
    def stop_engine(self):
        print("Motorcycle engine stopped")

# Using the classes
car = Car()
motorcycle = Motorcycle()

car.start_engine()
car.stop_engine()

motorcycle.start_engine()
motorcycle.stop_engine()
```

**Explanation:**

- **Abstract Base Class (Vehicle)**: Contains abstract methods `start_engine()` and `stop_engine()`.
- **Derived Classes (Car, Motorcycle)**: Implement the abstract methods.
- **Abstraction**: Users interact with the `start_engine()` and `stop_engine()` methods without needing to know how they are implemented.

conclusion
Object-Oriented Programming (OOP) is a programming paradigm centered around the concept of "objects" which can contain data, in the form of fields (often known as attributes), and code, in the form of procedures (often known as methods). The four key principles of OOP are encapsulation, inheritance, polymorphism, and abstraction. Encapsulation refers to bundling the data and methods that operate on the data within a single unit or class, and restricting access to some of the object's components, which helps maintain integrity and prevents unintended interference. Inheritance allows a new class to inherit attributes and methods from an existing class, fostering code reuse and logical hierarchy. Polymorphism enables methods to operate differently based on the object, allowing a unified interface to interact with different data types. Lastly, abstraction simplifies complex systems by exposing only the necessary parts, hiding the intricate details, and thus enhancing code readability and usability. Together, these principles promote modular, reusable, and maintainable code, making OOP a powerful and widely-used programming paradigm.

---
About Me:
🖇️<a href="https://www.linkedin.com/in/kammari-anand-504512230/">LinkedIn</a>
🧑‍💻<a href="https://www.github.com/kammarianand">GitHub</a>