Encapsulation in Python

Encapsulation is one of the fundamental principles of Object-Oriented Programming (OOP) in Python. It is used to restrict direct access to variables and methods, preventing accidental modifications. Encapsulation helps in data hiding, security, and modularity.

Understanding Encapsulation

Encapsulation involves wrapping data (variables) and methods (functions) within a class and controlling their accessibility. In Python, we achieve encapsulation using:

  • Public members (accessible anywhere)
  • Protected members (accessible within the class and subclasses, indicated by a single underscore _)
  • Private members (accessible only within the class, indicated by double underscores __)

Why Use Encapsulation?

  • **Data Protection** – Prevents direct modification of sensitive data.
  • **Better Code Organization** – Groups related data and methods.
  • **Controlled Access** – Defines how data should be used.

Examples

1. Public Members in a Class

In this example, we define a class with a public variable and access it directly.

We create a Car class with a public attribute brand. Since it is public, we can access and modify it outside the class.

</>
Copy
class Car:
    def __init__(self, brand):
        self.brand = brand  # Public attribute

# Create an object of Car
my_car = Car("Toyota")

# Access and modify the public attribute
print(my_car.brand)  # Output: Toyota
my_car.brand = "Honda"
print(my_car.brand)  # Output: Honda

Output

Toyota
Honda

2. Protected Members in a Class

In Python, protected members are indicated by a single underscore _. They can still be accessed, but it’s a convention that they shouldn’t be modified outside the class.

We create a BankAccount class with a protected attribute _balance. Although we can access it outside the class, it is intended to be used only within the class or subclasses.

</>
Copy
class BankAccount:
    def __init__(self, balance):
        self._balance = balance  # Protected attribute

    def get_balance(self):
        return self._balance  # Method to access balance

# Create an object
account = BankAccount(5000)

# Accessing the protected attribute (not recommended)
print(account._balance)  # Output: 5000

# Accessing balance via a method (preferred way)
print(account.get_balance())  # Output: 5000

Output

5000
5000

3. Private Members in a Class

Private members are indicated by a double underscore __. They are not accessible directly outside the class.

Here, we create a Person class with a private attribute __age.

When we try to access this private attribute __age directly, results in an error.

</>
Copy
class Person:
    def __init__(self, age):
        self.__age = age  # Private attribute

    def get_age(self):
        return self.__age  # Method to access private attribute

# Create an object
person = Person(30)

# Attempting direct access (will cause error)
print(person.__age)  # AttributeError

But when we try to access this private attribute __age using a method, everything should be fine.

</>
Copy
class Person:
    def __init__(self, age):
        self.__age = age  # Private attribute

    def get_age(self):
        return self.__age  # Method to access private attribute

# Create an object
person = Person(30)

# Accessing via method
print(person.get_age())  # Output: 30

Output

30

4. Name Mangling to Access Private Members

Although private attributes cannot be accessed directly, Python uses name mangling to store them with a modified name. We can access them using the format _ClassName__attribute, but this is not recommended.

</>
Copy
class Employee:
    def __init__(self, salary):
        self.__salary = salary  # Private attribute

# Create an object
emp = Employee(50000)

# Access private attribute using name mangling
print(emp._Employee__salary)  # Output: 50000

Output

50000

5. Using Getters and Setters

To follow encapsulation principles, we use getter and setter methods to access and modify private attributes safely.

We create a Student class where __marks is private. We define get_marks() to retrieve the value and set_marks() to modify it with validation.

</>
Copy
class Student:
    def __init__(self, marks):
        self.__marks = marks  # Private attribute

    def get_marks(self):
        return self.__marks  # Getter method

    def set_marks(self, new_marks):
        if 0 <= new_marks <= 100:  # Validation
            self.__marks = new_marks
        else:
            print("Invalid marks! Must be between 0 and 100.")

# Create an object
student = Student(85)

# Accessing marks using getter
print(student.get_marks())  # Output: 85

# Updating marks using setter
student.set_marks(95)
print(student.get_marks())  # Output: 95

# Attempting invalid update
student.set_marks(150)  # Output: Invalid marks! Must be between 0 and 100.

Output

85
95
Invalid marks! Must be between 0 and 100.

6. Encapsulation in Real-world Applications

Encapsulation is widely used in large applications for data security and modularity. For example, in banking systems, user account details should not be directly modified; they should be accessed via methods.

</>
Copy
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # Private attribute

    def deposit(self, amount):
        self.__balance += amount

    def withdraw(self, amount):
        if self.__balance >= amount:
            self.__balance -= amount
        else:
            print("Insufficient balance!")

    def get_balance(self):
        return self.__balance

# Creating an account
account = BankAccount(1000)
account.deposit(500)
print(account.get_balance())  # Output: 1500

Output

1500