Python Encapsulation

Ka Kavitha V Updated 03 Oct 2026
7 min read

Encapsulation

Encapsulation is an object-oriented programming principle that bundles data and the methods that work on it into one unit — a class — and controls how that data can be accessed and changed.

Think of a bank account. You cannot reach into the bank's database and type a new balance. Instead, you use controlled operations — deposit, withdraw, check balance — and each one enforces the bank's rules. Encapsulation brings the same idea to your code.

In Python, encapsulation:

  • Keeps data (variables) and behavior (methods) together inside a class
  • Restricts direct access to sensitive or internal data
  • Protects objects from accidental misuse, such as setting an invalid value

The result is code that is safer, easier to control, and easier to maintain.

The Problem Encapsulation Solves

Without encapsulation, any code can change an object's data to anything:

class Account:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance

acc = Account("Rahul", 10000)
acc.balance = -50000      # Nothing stops an invalid value
print(acc.balance)

Expected output:

-50000

The object is now in an invalid state. Encapsulation prevents this by hiding the data and allowing changes only through methods that check the rules.

Access Levels in Python

Python does not have keywords like private or public. Instead, it uses naming conventions with underscores:

NameLevelMeaning
balancePublicFree to use from anywhere
_balanceProtected (by convention)Intended for internal use; "please don't touch from outside"
__balancePrivate (name-mangled)Python changes the name to make outside access harder

Private Properties

To make a property private, start its name with two underscores (__).

Example: Creating a Private Property

class Account:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance   # Private property

acc = Account("Rahul", 10000)

print(acc.owner)
print(acc.__balance)

Output:

Rahul
AttributeError: 'Account' object has no attribute '__balance'
  • acc.owner is public, so it can be read normally.
  • acc.__balance cannot be accessed directly from outside the class.

Note: Code inside the class can still use self.__balance normally. Only access from outside is blocked.

Accessing Private Data With a Getter Method

To allow reading a private value safely, provide a getter method.

Example: A Getter Method

class Account:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance

    def get_balance(self):
        return self.__balance

acc = Account("Sneha", 15000)
print(acc.get_balance())

Expected output:

15000

The getter is defined inside the class, so it can read self.__balance and return it. Outside code can see the balance but cannot directly change it.

Modifying Private Data With a Setter Method

To allow changing private data in a controlled way, provide a setter method. A setter can validate a value before saving it.

Example: A Setter With Validation

class Account:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance

    def get_balance(self):
        return self.__balance

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

acc = Account("Sneha", 15000)
print(acc.get_balance())

acc.set_balance(18000)
print(acc.get_balance())

acc.set_balance(-500)
print(acc.get_balance())

Expected output:

15000
18000
Balance cannot be negative
18000

What to notice:

  • The valid value 18000 is accepted and saved.
  • The invalid value -500 is rejected, and the balance stays 18000.
  • Because the only way to change the balance is through set_balance(), the rule "balance cannot be negative" is always enforced.

Tip: In real applications, raising an exception (raise ValueError("Balance cannot be negative")) is often better than printing a message, because it forces the calling code to deal with the problem.

Why Encapsulation Is Important

  • Data protection — internal data cannot be changed accidentally from outside.
  • Validation — every change passes through methods that enforce the rules.
  • Flexibility — the internal implementation can change (for example, storing the balance in paise instead of rupees) without affecting the code that uses the class, as long as the methods stay the same.
  • Control — the class decides exactly how its data is read and modified.

Practical Example: Encapsulating Exam Marks

class ExamResult:
    def __init__(self, student_name):
        self.student_name = student_name
        self.__marks = 0

    def set_marks(self, marks):
        if 0 <= marks <= 100:
            self.__marks = marks
        else:
            print("Marks must be between 0 and 100")

    def get_marks(self):
        return self.__marks

    def get_result(self):
        return "Pass" if self.__marks >= 40 else "Fail"

result = ExamResult("Amit")
result.set_marks(78)

print(result.get_marks())
print(result.get_result())

result.set_marks(150)
print(result.get_marks())

Expected output:

78
Pass
Marks must be between 0 and 100
78

How the class protects its data:

  • __marks starts at 0 and can only be changed through set_marks().
  • set_marks() accepts only values from 0 to 100, so 150 is rejected.
  • get_result() calculates the result from the private marks. Outside code never needs to know how the pass mark is decided — if the rule changes, only this method changes.

Protected Properties

A single underscore (_) marks a property as protected. This is purely a convention that tells other developers: "this is for internal use — don't use it directly from outside the class."

Protected properties:

  • Are meant for use inside the class and its subclasses
  • Can still be accessed from outside, but should not be

Example: A Protected Property

class Employee:
    def __init__(self, name, bonus):
        self.name = name
        self._bonus = bonus   # Protected property

emp = Employee("Kiran", 5000)
print(emp.name)
print(emp._bonus)   # Allowed, but discouraged

Expected output:

Kiran
5000

Note: Python does not enforce protected access. The single underscore is a signal to other programmers, not a rule the interpreter checks. In practice, the single underscore is the most common way Python developers mark internal attributes.

Private Methods

Methods can also be made private with a double underscore. Private methods are helper methods used inside the class, not meant to be called from outside.

Example: A Private Method

class MathTool:
    def __init__(self):
        self.total = 0

    def __is_valid(self, value):
        return isinstance(value, (int, float))

    def add(self, value):
        if self.__is_valid(value):
            self.total += value
        else:
            print("Invalid input")

tool = MathTool()
tool.add(10)
tool.add(5)
tool.add("ten")
print(tool.total)

Expected output:

Invalid input
15
  • add() is public — it is the method outside code is meant to use.
  • __is_valid() is private — an internal check that add() relies on.
  • "ten" is rejected because it is neither an int nor a float.

Calling the private method from outside fails:

tool.__is_valid(5)

Output:

AttributeError: 'MathTool' object has no attribute '__is_valid'

Name Mangling

Python implements private names with a technique called name mangling. Inside a class, any name that starts with two underscores (and does not end with two underscores) is automatically renamed:

__value   becomes   _ClassName__value

This is why acc.__balance is "not found" from outside: the attribute's real name is _Account__balance.

Example: Seeing Name Mangling

class User:
    def __init__(self, username, pin):
        self.username = username
        self.__pin = pin

user = User("admin", 4321)

print(user.__dict__)
print(user._User__pin)   # Works, but not recommended

Expected output:

{'username': 'admin', '_User__pin': 4321}
4321
  • __dict__ shows the object's real attribute names, including the mangled _User__pin.
  • Using the mangled name bypasses the protection — which shows that Python's privacy is not a security feature. It prevents accidental access and avoids name clashes in subclasses; it does not stop deliberate access.

Important: Never rely on private attributes to protect secrets such as passwords or PINs. Sensitive data needs real security measures, such as hashing and access control.

A Subtle Trap

Assigning to obj.__name from outside the class creates a brand-new, unrelated attribute — it does not change the private one:

acc = Account("Rahul", 10000)
acc.__balance = 0          # Creates a NEW attribute named '__balance'
print(acc.get_balance())   # The real private balance is unchanged

Expected output:

10000

Pythonic Encapsulation With @property

Writing get_balance() and set_balance() methods works, but Python offers a more natural approach: the @property decorator. It lets callers use attribute syntax while the class still runs validation code behind the scenes.

class Account:
    def __init__(self, owner, balance):
        self.owner = owner
        self.__balance = balance

    @property
    def balance(self):
        return self.__balance

    @balance.setter
    def balance(self, amount):
        if amount < 0:
            raise ValueError("Balance cannot be negative")
        self.__balance = amount

acc = Account("Sneha", 15000)
print(acc.balance)       # Calls the getter

acc.balance = 18000      # Calls the setter
print(acc.balance)

Expected output:

15000
18000

Setting acc.balance = -100 would raise ValueError: Balance cannot be negative. The calling code looks as simple as using a public attribute, but every change is validated.

Common Mistakes

MistakeProblem
Expecting _name to block accessSingle underscore is only a convention
Treating __name as real securityName mangling can be bypassed with _ClassName__name
Assigning obj.__name = value from outsideCreates a new attribute; the private one is unchanged
Making everything privateAdds unnecessary getters and setters; protect only what needs rules
Setters that silently accept invalid dataDefeats the purpose of encapsulation — always validate
  • Python Class Properties — instance and class attributes
  • Python Class Methods — the public interface of a class
  • Python Inheritance — how protected and private names behave in subclasses
  • Python Functions — decorators such as @property

0 Comments

Reviewed before they appear

No comments yet.

Python
Ask about this post
AI Ask about this post

Ask questions about Python Encapsulation and get answers drawn from it.

Signed-in readers only.