Lesson 54 of 70 – Python Encapsulation
77%

Python Encapsulation

Encapsulation is an important concept of Object-Oriented Programming (OOP). It means bundling data and the methods that operate on that data inside a class.

Encapsulation also helps control how the data of an object can be accessed or modified. In Python, encapsulation is commonly implemented using public, protected, and private naming conventions.

Note: Python does not enforce access modifiers as strictly as languages such as Java or C++. Instead, Python uses naming conventions and name mangling to indicate restricted access.
What is Encapsulation?

Encapsulation means keeping data and the methods that work with that data together inside a class.

It also provides a way to control direct access to the internal data of an object.

class Student:

    def __init__(self, name, marks):
        self.name = name
        self.marks = marks

    def display(self):
        print(self.name)
        print(self.marks)


s1 = Student("Rahul", 85)

s1.display()
Output:
Rahul
85
Why is Encapsulation Used?

Encapsulation provides several benefits:

  • Protects internal data.
  • Controls how data is modified.
  • Combines data and methods in one unit.
  • Makes programs easier to maintain.
  • Reduces accidental modification of data.
  • Improves code organization.
Encapsulation in Python

Python mainly uses three naming conventions for data members:

  • Public – accessible normally.
  • Protected – indicated using a single underscore _.
  • Private – indicated using double underscores __.
Public Members

A public member can be accessed directly from outside the class.

class Student:

    def __init__(self):
        self.name = "Rahul"


student = Student()

print(student.name)
Output:
Rahul

Here, name is a public attribute.

Public Attribute Example
class Account:

    def __init__(self):
        self.balance = 5000


account = Account()

print(account.balance)

account.balance = 7000

print(account.balance)
Output:
5000
7000

Because balance is public, it can be accessed and modified directly.

Protected Members

A protected member is normally written with a single underscore before its name.

class Student:

    def __init__(self):
        self._marks = 85


student = Student()

print(student._marks)
Output:
85

The single underscore is mainly a convention that tells programmers that the member is intended for internal or subclass use.

Protected Member Example
class Parent:

    def __init__(self):
        self._value = 100


class Child(Parent):

    def show(self):
        print(self._value)


obj = Child()

obj.show()
Output:
100

A subclass can normally access a protected-style attribute.

Private Members

A private member is written using two underscores before its name.

class Student:

    def __init__(self):
        self.__marks = 90


student = Student()

print(student.__marks)

The above code produces an error because __marks is name-mangled.

Important: A double underscore does not provide absolute security. Python changes the internal name of the attribute using name mangling.
Name Mangling

When an attribute starts with two underscores, Python performs name mangling.

class Student:

    def __init__(self):
        self.__marks = 90


student = Student()

print(student._Student__marks)
Output:
90

Python internally changes __marks to approximately _Student__marks.

Why Use Private Attributes?

Private attributes are useful when you want to prevent normal direct access to internal implementation details.

class BankAccount:

    def __init__(self, balance):
        self.__balance = balance

    def show_balance(self):
        print(self.__balance)


account = BankAccount(10000)

account.show_balance()
Output:
10000
Encapsulation Using Methods

Instead of allowing direct modification of private data, we can provide methods to control access.

class BankAccount:

    def __init__(self, balance):
        self.__balance = balance

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

    def show_balance(self):
        print(self.__balance)


account = BankAccount(5000)

account.deposit(2000)

account.show_balance()
Output:
7000
Getter Method

A getter method is used to retrieve the value of an internal attribute.

class Student:

    def __init__(self):
        self.__marks = 85

    def get_marks(self):
        return self.__marks


student = Student()

print(student.get_marks())
Output:
85
Setter Method

A setter method can be used to change the value of an internal attribute.

class Student:

    def __init__(self):
        self.__marks = 0

    def set_marks(self, marks):
        self.__marks = marks

    def get_marks(self):
        return self.__marks


student = Student()

student.set_marks(90)

print(student.get_marks())
Output:
90
Getter and Setter with Validation

Encapsulation allows us to validate data before storing it.

class Student:

    def __init__(self):
        self.__marks = 0

    def set_marks(self, marks):

        if 0 <= marks <= 100:
            self.__marks = marks
        else:
            print("Invalid marks")

    def get_marks(self):
        return self.__marks


student = Student()

student.set_marks(85)

print(student.get_marks())
Output:
85
Preventing Invalid Data
class BankAccount:

    def __init__(self):
        self.__balance = 0

    def set_balance(self, amount):

        if amount >= 0:
            self.__balance = amount
        else:
            print("Balance cannot be negative")

    def get_balance(self):
        return self.__balance


account = BankAccount()

account.set_balance(-500)

print(account.get_balance())
Output:
Balance cannot be negative
0
Encapsulation with Property

Python provides the @property decorator to create controlled attribute access.

class Student:

    def __init__(self, marks):
        self.__marks = marks

    @property
    def marks(self):
        return self.__marks


student = Student(90)

print(student.marks)
Output:
90
Property Setter

The @property decorator can be combined with @marks.setter to control assignment.

class Student:

    def __init__(self, marks):
        self.__marks = marks

    @property
    def marks(self):
        return self.__marks

    @marks.setter
    def marks(self, value):

        if 0 <= value <= 100:
            self.__marks = value
        else:
            print("Invalid marks")


student = Student(80)

student.marks = 95

print(student.marks)
Output:
95
Real-Life Example of Encapsulation

Consider a bank account. The account balance should not normally be changed directly. Instead, operations such as deposit and withdrawal can control how the balance changes.

class BankAccount:

    def __init__(self, balance):
        self.__balance = balance

    def deposit(self, amount):

        if amount > 0:
            self.__balance += amount

    def withdraw(self, amount):

        if 0 < amount <= self.__balance:
            self.__balance -= amount
        else:
            print("Invalid withdrawal")

    def get_balance(self):
        return self.__balance


account = BankAccount(10000)

account.deposit(2000)
account.withdraw(3000)

print(account.get_balance())
Output:
9000
Encapsulation and Data Protection

Encapsulation helps protect the internal state of an object by controlling how data is accessed or changed.

class Employee:

    def __init__(self, salary):
        self.__salary = salary

    def get_salary(self):
        return self.__salary

    def set_salary(self, salary):

        if salary > 0:
            self.__salary = salary


employee = Employee(30000)

employee.set_salary(35000)

print(employee.get_salary())
Output:
35000
Public vs Protected vs Private
Type Syntax Meaning
Public name Normally accessible from anywhere
Protected _name Intended for internal or subclass use
Private __name Name-mangled to discourage direct access
Encapsulation Example with Employee
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("Invalid salary")


employee = Employee("Amit", 40000)

print(employee.name)

print(employee.get_salary())

employee.set_salary(45000)

print(employee.get_salary())
Output:
Amit
40000
45000
Important Points About Encapsulation
  • Encapsulation combines data and methods inside a class.
  • Public members can normally be accessed directly.
  • A single underscore indicates a protected-style member by convention.
  • A double underscore triggers name mangling.
  • Private attributes can be accessed through methods such as getters and setters.
  • Encapsulation can be used to validate data.
  • The @property decorator provides convenient controlled attribute access.
  • Python does not provide strict private access control like some other programming languages.
Key Points
  • Encapsulation is an important OOP concept.
  • It keeps data and related methods together.
  • Public attributes use normal names.
  • Protected-style attributes use a single underscore.
  • Private-style attributes use two underscores.
  • Python uses name mangling for double-underscore attributes.
  • Getters and setters can control access to data.
  • Properties provide a Pythonic way to implement controlled attributes.

🧠 Quick Quiz

Question: Which naming style is commonly used for a private attribute in Python?