Encapsulation and Data Hiding in Python (OOP)

In Object-Oriented Programming (OOP), one of the core principles is Encapsulation. It refers to the idea of wrapping data (attributes) and methods (functions) into a single unit called a class.

Encapsulation helps in data protection, ensuring that an object's internal state is hidden from the outside world — a concept known as Data Hiding. This promotes better control, security, and modularity in your Python programs.

What is Encapsulation?

Encapsulation is the mechanism of binding data and code together within a class and restricting direct access to the data from outside the class. Instead, you interact with the data using defined methods (getters, setters, etc.), ensuring controlled modification.

Key idea: Protect internal object details and expose only necessary parts through a clean interface.

Example

class Student:
    def __init__(self, name, marks):
        self.name = name  # Public attribute
        self.__marks = marks  # Private attribute

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

    def set_marks(self, marks):  # Setter method
        if 0 <= marks <= 100:
            self.__marks = marks
        else:
            print("Invalid marks!")


s1 = Student("Alice", 85)
print(s1.name)  # Accessing public member
print(s1.get_marks())  # Accessing private member via getter

s1.set_marks(95)
print(s1.get_marks())
Output:

Alice
85
95
- The private attribute __marks cannot be accessed directly.
- Access and modification are done through controlled methods (get_marks and set_marks).

What is Data Hiding?

Data Hiding is the practice of restricting direct access to class attributes and methods to prevent accidental or unauthorized modification. It's an implementation of encapsulation — hiding "how" things work internally.

Python achieves data hiding using access modifiers like:

- Public
- Protected
- Private

Access Modifiers in Python

Python doesn't have strict access control like other languages (e.g., Java or C++), but it follows naming conventions to indicate the level of access.

Access Type Syntax Accessible From Description
Public variable Anywhere (inside/outside class) No restriction on access
Protected _variable Within class and subclasses Conventionally restricted
Private __variable Only within the class Name-mangled to prevent external access

Public Members

Public members are accessible from anywhere — inside or outside the class.

class Car:
    def __init__(self, brand, model):
        self.brand = brand    # Public
        self.model = model    # Public

c1 = Car("Tesla", "Model S")
print(c1.brand)   # Accessible
print(c1.model)   # Accessible
- Public members are the default in Python.
- They are intended to be openly accessed and modified if needed.

Protected Members

Protected members are prefixed with a single underscore (_). They can still be accessed outside the class, but by convention, they should only be used within the class or its subclasses.

class Vehicle:
    def __init__(self):
        self._speed = 60    # Protected member

class Car(Vehicle):
    def show_speed(self):
        print("Speed:", self._speed)

c = Car()
c.show_speed()
print(c._speed)   # Technically accessible, but not recommended
Output:

Speed: 60
60
- Python doesn't enforce protection strictly — it's a convention indicating internal use.
- In larger codebases, this helps developers maintain discipline.

Private Members

Private members are prefixed with double underscores (__). They are name-mangled internally to prevent accidental access from outside the class.

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

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

    def get_balance(self):
        return self.__balance

acc = BankAccount(5000)
acc.deposit(1500)
print(acc.get_balance())

# Direct access (will cause an error)
# print(acc.__balance)  # AttributeError
Output:

6500
- Attempting to access __balance directly will cause an AttributeError.
- Internally, Python renames it to _ClassName__attribute, a process called name mangling.

Name Mangling Example

Name mangling in Python is a mechanism that modifies the names of class attributes (variables and methods) that start with two leading underscores (e.g., __attribute_name).

The name mangling process helps to access the class variables from outside the class.

Name mangling does not provide true privacy like private keywords in other languages (e.g., Java, C++). The mangled name can still be accessed directly if you know the convention (e.g., _ClassName__attribute_name).

class Example:
    def __init__(self):
        self.__secret = "Hidden Value"

obj = Example()
# Accessing via name mangling
print(obj._Example__secret)
Output:

Hidden Value
- Name mangling changes __secret to _Example__secret.
- This is possible, but not recommended for regular use — it breaks encapsulation.

Real-World Example: Data Hiding in Practice

This example shows how data hiding protects sensitive information like account balance and number from direct access. Instead, controlled access is provided through public methods such as deposit(), withdraw(), and get_balance().

class Bank:
    def __init__(self, account_number, balance):
        self.__account_number = account_number
        self.__balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print(f"Deposited: {amount}")
        else:
            print("Invalid deposit amount!")

    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            print(f"Withdrawn: {amount}")
        else:
            print("Insufficient balance or invalid amount!")

    def get_balance(self):
        return self.__balance


b1 = Bank("ACC123", 5000)
b1.deposit(2000)
print("Current Balance:", b1.get_balance())

# Attempt to access private data
# print(b1.__balance)  # ❌ Not allowed
Output:

Deposited: 2000
Current Balance: 7000
- Only authorized methods (deposit, withdraw, get_balance) can interact with private data.
- This ensures that object data remains consistent and safe.

Getters and Setters in Python

Getters and setters are methods used to access and modify private data safely.

class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age > 0:
            self.__age = age
        else:
            print("Invalid age!")

p = Person("Alice", 25)
print(p.get_age())
p.set_age(30)
print(p.get_age())
- These methods ensure that invalid data cannot be assigned directly.
- They enforce rules on how private data should be modified.

25
30

Summary

Encapsulation and Data Hiding are at the heart of building robust, secure, and maintainable Python applications. They help you design cleaner APIs, protect object integrity, and simplify debugging.

Concept Description
Encapsulation Wrapping data and methods in a class
Data Hiding Restricting direct access to internal data
Public Members Accessible everywhere
Protected Members Intended for class and subclass use
Private Members Accessible only within the class
Name Mangling Internal renaming of private attributes
Getters/Setters Control data access and modification
Remember:

- Use public members for open access.
- Use protected members for subclass extension.
- Use private members for sensitive data.

Together, they form the foundation of information security and object integrity in Python OOP.
Share this Article