Encapsulation

Updated Nov 22, 2025

Encapsulation

Encapsulation means bundling attributes and methods together inside a class, and controlling access to them. It's like putting your belongings in a box - you decide what others can see and use.

To demonstrate encapsulation, consider the following example where we want to manage a bank account:

❌ Without Encapsulation

# bank_account.py # Account data is directly accessible and can be modified incorrectly account_balance = 1000 account_owner = "Alice" def withdraw(amount): global account_balance account_balance -= amount print(f"Withdrew ${amount}. New balance: ${account_balance}") # Problem: Anyone can directly change the balance account_balance = -500 # This shouldn't be allowed! withdraw(1000) # This could make balance negative!

Problem: The balance can be changed directly, even to invalid values like negative numbers. There's no protection or validation.

✅ With Encapsulation

# bank_account.py class BankAccount: def __init__(self, owner, initial_balance): self.owner = owner self.__balance = initial_balance # Private attribute (starts with __) def get_balance(self): return self.__balance def deposit(self, amount): if amount > 0: self.__balance += amount print(f"Deposited ${amount}. New balance: ${self.__balance}") else: print("Deposit amount must be positive!") def withdraw(self, amount): if amount > 0 and amount <= self.__balance: self.__balance -= amount print(f"Withdrew ${amount}. New balance: ${self.__balance}") else: print("Invalid withdrawal amount or insufficient funds!")
# main.py from bank_account import BankAccount account = BankAccount("Alice", 1000) # Can't directly access __balance # account.__balance = -500 # This won't work! # Must use the provided methods account.deposit(500) account.withdraw(200) print(f"Current balance: ${account.get_balance()}") # Invalid operations are prevented account.withdraw(2000) # Will show error message

Now:

  • The balance is protected - can't be changed directly.
  • All changes go through validated methods.
  • Invalid operations are prevented automatically.

This approach makes the code:

  • ✅ More secure - data can't be corrupted
  • ✅ More reliable - validation ensures correct values
  • ✅ Easier to maintain - all logic is in one place

🎁 Bonus: Public, Protected, and Private in Python

Python does not enforce strict access control like Java or C++.
Instead, it uses naming conventions to indicate intended access levels.

The goal is encapsulation, but Python trusts developers to follow conventions rather than forcing restrictions.


Public Attributes (No Underscore)

self.balance = 1000 # public
  • Accessible from anywhere
  • No protection
  • Meant to be part of the class API

Protected Attributes (_single_underscore)

self._balance = 1000 # protected (by convention)
  • Indicates “internal use”
  • Still fully accessible from outside
  • Python does NOT enforce any restriction

Example:

account._balance = -500 # Allowed, but not recommended

Private Attributes (__double_underscore)

self.__balance = 1000 # private (name-mangled)

Double underscore triggers name mangling:

self.__balance  →  self._BankAccount__balance

This hides the attribute but does not make it truly private.

You can still access it:

account._BankAccount__balance = -500 # Works!

Summary Table

LevelSyntaxAccessible?Enforced?Notes
PublicbalanceEverywhere❌ NoPart of API
Protected_balanceEverywhere❌ NoConvention
Private__balanceMangled⚠️ PartialPrevents accidental access

Final Takeaway

Python focuses on intent, not strict enforcement.
Encapsulation is achieved through conventions, not hard restrictions.