What is Encapsulation?
Encapsulation is an important concept in Object-Oriented Programming (OOP). It means combining data and the methods that operate on that data inside a class, while controlling how the data is accessed or modified.
In Python, encapsulation is commonly implemented using public, protected, and private naming conventions, along with methods, properties, and name mangling.
Direct Access
Internal / Subclass Use
Controlled Access
Data Protection
Encapsulation helps control how important object data is accessed and changed.
Public Members
Public attributes and methods can normally be accessed directly from outside the class.
Protected Members
A single underscore indicates that a member is intended for internal or subclass use.
Private Members
Double underscores trigger Python's name-mangling mechanism for stronger access restriction.
A class can keep its data and the methods that work with that data together.
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
def show_details(self):
print("Name:", self.name)
print("Marks:", self.marks)
student = Student("Rahul", 85)
student.show_details()
Name: Rahul
Marks: 85
The student data and the methods that operate on that
data are grouped inside the Student class.
By default, attributes and methods in Python are public. They can be accessed directly from outside the class.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
employee = Employee("Priya", 60000)
print(employee.name)
print(employee.salary)
employee.salary = 65000
print(employee.salary)
Priya
60000
65000
Because name and salary are public,
they can be accessed and modified directly.
A single underscore before an attribute or method is a convention indicating that it is intended for internal use or use by subclasses.
class Employee:
def __init__(self, name, salary):
self.name = name
self._salary = salary
def show_salary(self):
print("Salary:", self._salary)
employee = Employee("Amit", 55000)
employee.show_salary()
print(employee._salary)
Salary: 55000
55000
A double underscore before an attribute causes Python to apply name mangling. This is commonly used when you want to discourage direct access from outside the class.
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def show_balance(self):
print("Balance:", self.__balance)
account = BankAccount(50000)
account.show_balance()
Balance: 50000
The __balance attribute is intended to be
accessed through methods provided by the class.
Banking systems should not allow arbitrary changes to an account balance. Instead, deposits and withdrawals can be controlled through methods.
class BankAccount:
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("Deposited:", amount)
def withdraw(self, amount):
if amount > 0 and amount <= self.__balance:
self.__balance -= amount
print("Withdrawn:", amount)
else:
print("Insufficient balance")
def get_balance(self):
return self.__balance
account = BankAccount("SB1001", 50000)
account.deposit(10000)
account.withdraw(15000)
print("Current Balance:", account.get_balance())
Deposited: 10000
Withdrawn: 15000
Current Balance: 45000
The balance is protected from direct modification through
the private __balance attribute.
Getter and setter methods provide controlled access to private data.
class Student:
def __init__(self, name, marks):
self.__name = name
self.__marks = marks
def get_marks(self):
return self.__marks
def set_marks(self, marks):
if 0 <= marks <= 100:
self.__marks = marks
else:
print("Invalid marks")
student = Student("Sneha", 85)
print("Marks:", student.get_marks())
student.set_marks(92)
print("Updated Marks:", student.get_marks())
Marks: 85
Updated Marks: 92
The setter validates the value before changing the private attribute.
Employee salary is sensitive information. A setter can control whether a new salary value is accepted.
class Employee:
def __init__(self, name, salary):
self.name = name
self.__salary = salary
def get_salary(self):
return self.__salary
def set_salary(self, salary):
if salary >= 0:
self.__salary = salary
else:
print("Salary cannot be negative")
employee = Employee("Rahul", 50000)
print("Salary:", employee.get_salary())
employee.set_salary(60000)
print("Updated Salary:", employee.get_salary())
Salary: 50000
Updated Salary: 60000
An ATM should control access to sensitive information such as the account balance and PIN.
class ATM:
def __init__(self, pin, balance):
self.__pin = pin
self.__balance = balance
def check_pin(self, entered_pin):
return entered_pin == self.__pin
def check_balance(self, entered_pin):
if self.check_pin(entered_pin):
print("Balance:", self.__balance)
else:
print("Invalid PIN")
def withdraw(self, entered_pin, amount):
if not self.check_pin(entered_pin):
print("Invalid PIN")
elif amount <= 0:
print("Invalid amount")
elif amount > self.__balance:
print("Insufficient balance")
else:
self.__balance -= amount
print("Withdrawn:", amount)
print("Remaining Balance:", self.__balance)
atm = ATM(1234, 30000)
atm.check_balance(1234)
atm.withdraw(1234, 5000)
Balance: 30000
Withdrawn: 5000
Remaining Balance: 25000
The ATM class controls access to both the PIN and balance through methods.
Python's @property decorator provides a clean
way to create controlled getter and setter behavior.
class Product:
def __init__(self, price):
self.__price = price
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if value >= 0:
self.__price = value
else:
print("Price cannot be negative")
product = Product(1000)
print("Price:", product.price)
product.price = 1200
print("Updated Price:", product.price)
Price: 1000
Updated Price: 1200
The property allows the attribute to be accessed using normal dot notation while keeping validation logic inside the class.
An online store can use encapsulation to prevent invalid product prices or stock quantities.
class Product:
def __init__(self, name, price, stock):
self.name = name
self.__price = price
self.__stock = stock
def get_price(self):
return self.__price
def get_stock(self):
return self.__stock
def update_stock(self, quantity):
if quantity >= 0:
self.__stock = quantity
else:
print("Stock cannot be negative")
product = Product("Laptop", 55000, 20)
print("Product:", product.name)
print("Price:", product.get_price())
print("Stock:", product.get_stock())
product.update_stock(15)
print("Updated Stock:", product.get_stock())
Product: Laptop
Price: 55000
Stock: 20
Updated Stock: 15
A method beginning with double underscores is name-mangled and is intended to be used internally by the class.
class Order:
def __init__(self, price, tax):
self.price = price
self.tax = tax
def __calculate_total(self):
return self.price + self.tax
def show_total(self):
total = self.__calculate_total()
print("Total:", total)
order = Order(1000, 180)
order.show_total()
Total: 1180
The internal calculation is hidden behind the public
show_total() method.
A hospital application can use encapsulation to control access to patient information.
class Patient:
def __init__(self, name, age, disease):
self.name = name
self.age = age
self.__disease = disease
def get_disease(self):
return self.__disease
def update_disease(self, disease):
if disease:
self.__disease = disease
def show_details(self):
print("Name:", self.name)
print("Age:", self.age)
print("Disease:", self.__disease)
patient = Patient("Amit", 35, "Fever")
patient.show_details()
patient.update_disease("Viral Infection")
patient.show_details()
Name: Amit
Age: 35
Disease: Fever
Name: Amit
Age: 35
Disease: Viral Infection
Python transforms a private attribute such as
__balance into a name containing the class
name. This mechanism is called name mangling.
class Account:
def __init__(self, balance):
self.__balance = balance
def show_balance(self):
print("Balance:", self.__balance)
account = Account(25000)
account.show_balance()
print(account._Account__balance)
Balance: 25000
25000
Encapsulated data can also be used inside inherited classes through public or protected methods.
class Employee:
def __init__(self, name, salary):
self.name = name
self.__salary = salary
def get_salary(self):
return self.__salary
class Manager(Employee):
def show_salary(self):
print("Manager Salary:", self.get_salary())
manager = Manager("Priya", 85000)
manager.show_salary()
Manager Salary: 85000
The child class uses the parent's public method instead of directly accessing the private attribute.
One of the biggest practical benefits of encapsulation is validating data before storing it.
class BankAccount:
def __init__(self, balance):
self.__balance = 0
self.set_balance(balance)
def get_balance(self):
return self.__balance
def set_balance(self, balance):
if balance >= 0:
self.__balance = balance
else:
print("Invalid balance")
account = BankAccount(10000)
print("Balance:", account.get_balance())
account.set_balance(-5000)
print("Balance:", account.get_balance())
Balance: 10000
Invalid balance
Balance: 10000
The validation prevents an invalid negative balance from replacing the existing valid value.
This example combines private data, getter and setter methods, validation, and business logic in one employee management system.
class Employee:
def __init__(self, name, employee_id, salary):
self.name = name
self.employee_id = employee_id
self.__salary = salary
def get_salary(self):
return self.__salary
def set_salary(self, salary):
if salary >= 0:
self.__salary = salary
else:
print("Invalid salary")
def give_increment(self, percentage):
if percentage > 0:
increment = self.__salary * percentage / 100
self.__salary += increment
print("Increment:", increment)
def show_details(self):
print("Name:", self.name)
print("Employee ID:", self.employee_id)
print("Salary:", self.__salary)
employee = Employee(
"Rahul",
"EMP101",
60000
)
employee.show_details()
employee.set_salary(65000)
print()
employee.give_increment(10)
print()
employee.show_details()
Name: Rahul
Employee ID: EMP101
Salary: 60000
Increment: 6500.0
Name: Rahul
Employee ID: EMP101
Salary: 71500.0
The salary is encapsulated inside the
Employee class. External code interacts with
it through controlled methods instead of directly
modifying the internal value.
| Type | Syntax | Meaning | Access |
|---|---|---|---|
| Public | name | Normal public member. | Direct access is allowed. |
| Protected | _name | Intended for internal/subclass use. | Accessible, but convention says avoid direct external use. |
| Private | __name | Name-mangled member. | Direct access is discouraged and the original name is transformed. |
Rule 1: Keep related data and behavior together
A class should contain the data and methods that logically belong together.
Rule 2: Use private members for internal state
Use double underscores when you want Python's name mangling mechanism to discourage direct access.
Rule 3: Use methods for controlled access
Getter and setter methods can validate or transform values before changing internal state.
Rule 4: Use @property for clean interfaces
Properties allow controlled access while keeping the convenient attribute-style syntax.
Rule 5: Encapsulation is not the same as security
Python's private naming and name mangling are primarily tools for organization, preventing accidental access, and reducing unintended coupling.
Python Encapsulation - Real-Time Examples
Protect data. Control access. Build maintainable Python applications.