Python Encapsulation

Python Encapsulation

Encapsulation is a fundamental concept in object-oriented programming (OOP) that helps hide the internal details of an object, only exposing what is necessary. Think of it like a capsule that holds medicine: you don’t see the medicine directly, but you can use it to feel better.
In Python, encapsulation ensures that sensitive data is hidden from the user by:

    ● Declaring variables as private.
    ● Providing public methods (getters and setters) to access and update the value of a private field in the object.
    ● In Python, private variables and getter/setter methods are part of the concept of encapsulation, which restricts
       direct access to certain class attributes and methods to ensure the data is safe and controlled.
    ● A private variable in Python is defined by adding two underscores (__) before its name. A private variable cannot
       be accessed outside of the class directly.
    ● A getter method allows you to read the value of a private variable.
    ● A setter method is used to set or modify the value of a private variable, adding validation or conditions as
       needed.

Example:
Imagine you are developing a banking application for a small local bank. The application needs to manage customers’ bank accounts, allowing them to deposit and withdraw money and let them know how much amount they have currently when they deposit or withdraw money.

class BankAccount:
    def __init__(self, initial_balance):
          # Private variable to store the balance
          self.__balance = 0
          if initial_balance < 0:
                print(“Initial balance cannot be negative.”)
          else:
                self.__balance = initial_balance

       # Getter method to access the balance
       def balance(self):
             return self.__balance

       # Method to deposit money into the account
       def deposit(self, amount):
             if amount <= 0:
                print(“Deposit amount must be positive.”)
             else:
                self.__balance += amount
                         print(f”Deposited: ₹{amount:.2f}. New Balance: ₹{self.__balance:.2f}”)

    # Method to withdraw money from the account
    def withdraw(self, amount):
       if amount <= 0:
          print(“Withdrawal amount must be positive.”)
       elif amount > self.__balance:
          print(“Insufficient funds.”)
       else:
             self.__balance -= amount
             print(f”Withdrew: ₹{amount:.2f}. New Balance: ₹{self.__balance:.2f}”)

# Creating a BankAccount object with an initial balance of 1000
account = BankAccount(1000)
# Depositing money into the account
account.deposit(500)
# Withdrawing money from the account
account.withdraw(200)
# Printing the final balance
print(f”Final Balance: ₹{account.balance():.2f}”)

”’
Output:
Deposited: ₹500.00. New Balance: ₹1500.00
Withdrew: ₹200.00. New Balance: ₹1300.00
Final Balance: ₹1300.00
”’

Explanation:

class BankAccount:

This line defines a class called BankAccount.

   def __init__(self, initial_balance):
          self.__balance = 0
          if initial_balance < 0:
                print(“Initial balance cannot be negative.”)
          else:
                self.__balance = initial_balance

def __init__(self, initial_balance):: This is the constructor method that gets called when a new BankAccount object is created. It takes an argument initial_balance to initialize the balance of the account.

self.__balance = 0: This creates a private instance variable __balance and initializes it to 0. The double underscore (__) before balance makes this variable private, meaning it cannot be accessed directly from outside the class.

if initial_balance < 0:: This checks if the provided initial balance is negative.

print(“Initial balance cannot be negative.”): If the initial balance is negative, it prints an error message.

self.__balance = initial_balance: If the initial balance is valid (i.e., non-negative), it sets __balance to the provided initial_balance.

def balance(self):
      return self.__balance

def balance(self):: This is a getter method that allows access to the private __balance attribute.
return self.__balance: This returns the current balance of the account when called.

def deposit(self, amount):
        if amount <= 0:
               print(“Deposit amount must be positive.”)
        else:
               self.__balance += amount
        print(f”Deposited: ₹{amount:.2f}. New Balance: ₹{self.__balance:.2f}”)

def deposit(self, amount):: This method allows depositing money into the account. It takes an amount as an argument.
if amount <= 0:: It checks if the deposit amount is less than or equal to 0.
print(“Deposit amount must be positive.”): If the deposit amount is non-positive, it prints an error message.
self.__balance += amount: If the deposit amount is valid, it adds the amount to the current balance.
print(f”Deposited: ₹{amount:.2f}. New Balance: ₹{self.__balance:.2f}”): It prints a message showing the deposited amount and the new balance, formatted to two decimal places.

def withdraw(self, amount):
        if amount <= 0:
               print(“Withdrawal amount must be positive.”)
        elif amount > self.__balance:
               print(“Insufficient funds.”)
        else:
               self.__balance -= amount
               print(f”Withdrew: ₹{amount:.2f}. New Balance: ₹{self.__balance:.2f}”)

def withdraw(self, amount):: This method allows withdrawing money from the account. It takes an amount as an argument.
if amount <= 0:: It checks if the withdrawal amount is less than or equal to 0.
print(“Withdrawal amount must be positive.”): If the amount is non-positive, it prints an error message.
elif amount > self.__balance:: It checks if the withdrawal amount is greater than the current balance.
print(“Insufficient funds.”): If there are insufficient funds, it prints an error message.
self.__balance -= amount: If the withdrawal amount is valid and funds are sufficient, it subtracts the amount from the current balance.
print(f”Withdrew: ₹{amount:.2f}. New Balance: ₹{self.__balance:.2f}”): It prints a message showing the withdrawn amount and the new balance, formatted to two decimal places.

account = BankAccount(1000) 

This line creates an instance of the BankAccount class with an initial balance of ₹1000.

account.deposit(500)

This deposits ₹500 into the account.

account.withdraw(200) 

This withdraws ₹200 from the account.

print(f”Final Balance: ₹{account.balance():.2f}”)

 This prints the final balance of the account, formatted to two decimal places.

What will happen if we keep the balance variable public instead of private?

Example:

class BankAccount:
    def __init__(self, initial_balance):
          # Public variable to store the balance
          self.balance = 0
          if initial_balance < 0:
             print(“Initial balance cannot be negative.”)
    else:
          self.balance = initial_balance

    # method to access the balance
    def balance(self):
          return self.balance

    # Method to deposit money into the account
    def deposit(self, amount):
          if amount <= 0:
                print(“Deposit amount must be positive.”)
          else:
                self.balance += amount
                      print(f”Deposited: ₹{amount:.2f}. New Balance: ₹{self.balance:.2f}”)

    # Method to withdraw money from the account
    def withdraw(self, amount):
       if amount <= 0:
          print(“Withdrawal amount must be positive.”)
       elif amount > self.balance:
          print(“Insufficient funds.”)
       else:
          self.balance -= amount
                   print(f”Withdrew: ₹{amount:.2f}. New Balance: ₹{self.balance:.2f}”)

# Creating a BankAccount object with an initial balance of 1000
account = BankAccount(1000)
# Depositing money into the account
account.deposit(500)

# Withdrawing money from the account
account.withdraw(200)

# Accessing the balance directly (because it’s public)
print(f”Directly Accessed Balance: ₹{account.balance:.2f}”)

# Directly modifying the balance (which could lead to issues)
account.balance = 2000
# Invalid operation of modifying balance but allowed for public variables

print(f”After Direct Modification, Balance: ₹{account.balance:.2f}”)

Course Video

Course Video English:

Explanation:
In the above code, balance is now public, which means it can be accessed directly from outside the class. For example, account.balance can be printed or modified without any method being involved. Since the variable is public, it can be directly modified from outside the class. In the last part of the code, the balance is directly changed to 2000 (account.balance = 2000 ), which is not secure for a bank account (since anyone can type the line and change the balance). This would not be possible if the variable were private and hence, provide more security to code.

Task:
1. Basic Encapsulation:
Write a class Person with private fields for FirstName and LastName. Provide public properties with getters and setters to access and modify these fields. Ensure the setters validate that the names are not empty.

2. Read-Only Property:
Create a class Product with private fields for item name, price, and stockQuantity. Provide a public property item name that allows only read access ( a getter but no setter i.e. name of the item cannot be changed). The Price and StockQuantity properties should have both getters and setters i.e price and stock quantity can be accessed and changed as well.
For e.g. in a jewelry shop item is gold, price and stock quantity can be changed on daily basis

3. Encapsulation with Methods:
Write a class BankAccount with private fields for accountNumber, balance, and ownerName. Provide public methods to Deposit and Withdraw money. Ensure that the Withdraw method does not allow the balance to go negative.

4. Validation in Setters:
Create a class Student with private fields for studentId, name, and gpa. Implement public properties for these fields with validation logic in the setters (e.g., studentId should be positive, name should not be null or empty, and gpa should be between 0 and 4.0).

5. Private Methods:
Write a class LibraryBook with private fields for title, author, and isCheckedOut (Yes/ No). Provide public methods to CheckOut (Yes) and ReturnBook (No). Use private methods to update the status of the book.

6. Auto-Implemented Properties:
Create a class Car with auto-implemented properties for Make, Model, and Year. Ensure that the Year property cannot be set to a future year.

7. Encapsulating Collections:
Write a class Inventory that contains a private list of Item objects. Provide public methods to add and remove items, as well as to get a read-only list of items.
E.g. __vegetable_list = [‘potato’, ‘tomato’, ‘onion’]
1. Add cabbage in the list
2. Remove tomato from the list
3. Read whole list

8. Constructor Initialization:
Write a class Order with private fields for orderId, customerName, and orderTotal. Provide a constructor to initialize these fields and properties to access them. Ensure the fields are initialized with valid values.

9. Computed Properties:
Create a class Rectangle with private fields for width and height. Provide public properties for these fields. Add a read-only property Area that computes and returns the area of the rectangle based on width and height.

Task Video

YouTube Reference :

Frequently Asked Questions

Still have a question?

Let's talk

Encapsulation is the practice of restricting access to the inner workings of an object and only allowing access through specified methods.

Polymorphism allows objects to take many forms, while encapsulation hides the internal state of an object and only exposes a controlled interface.

The seven concepts are Encapsulation, Abstraction, Inheritance, Polymorphism, Classes, Objects, and Methods.

Interview questions typically focus on the principles of restricting access to an object’s data and methods through public interfaces.

It is the concept of bundling data (attributes) and methods (functions) that operate on the data within a class and restricting direct access to some of an object’s components.

Encapsulation helps protect data integrity, makes code more maintainable, and allows controlled access to object properties.

Encapsulation in OOP refers to keeping the internal state of an object hidden from the outside world and only allowing interaction via defined methods.

You can use encapsulation by declaring private variables (using _ or __) and providing public getter and setter methods to interact with those variables.

Overriding is the process of defining a method in a subclass that has the same name as a method in the parent class, thereby modifying its behavior.

Encapsulation in OOP is the concept of keeping the data (attributes) and methods that operate on the data within the same class and restricting access to some internal details.