Tutorials Logic, IN +91 8092939553 info@tutorialslogic.com
FAQs Support
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Interview Questions Website Development
Compiler Tutorials

Encapsulation in Python

What is Encapsulation?

Encapsulation bundles data (attributes) and the methods that operate on that data into a single unit (class), and restricts direct access to some components. It protects the internal state of an object from unintended modification.

Access Modifiers in Python

Python uses naming conventions rather than strict keywords like private or protected.

ConventionExampleMeaning
No prefixself.namePublic — accessible anywhere
Single underscore _self._salaryProtected — "don't touch from outside" (convention only)
Double underscore __self.__passwordPrivate — name-mangled, harder to access from outside
Access Modifiers
class BankAccount:
    def __init__(self, owner: str, balance: float):
        self.owner = owner          # public
        self._account_type = "savings"  # protected (convention)
        self.__balance = balance    # private (name-mangled)

    def deposit(self, amount: float):
        if amount > 0:
            self.__balance += amount

    def get_balance(self) -> float:
        return self.__balance       # controlled access

account = BankAccount("Alice", 1000)

print(account.owner)           # Alice  (public — fine)
print(account._account_type)   # savings (works but discouraged)
print(account.get_balance())   # 1000   (via method — correct way)

# Direct access to __balance fails
# print(account.__balance)     # AttributeError!

# Name mangling — Python renames it to _ClassName__attr
print(account._BankAccount__balance)  # 1000 (possible but bad practice)

Getters and Setters with @property

@property Decorator
class Temperature:
    def __init__(self, celsius: float = 0):
        self._celsius = celsius

    @property
    def celsius(self) -> float:
        """Getter"""
        return self._celsius

    @celsius.setter
    def celsius(self, value: float):
        """Setter with validation"""
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._celsius = value

    @property
    def fahrenheit(self) -> float:
        """Computed property — no setter needed"""
        return self._celsius * 9/5 + 32

    @property
    def kelvin(self) -> float:
        return self._celsius + 273.15

t = Temperature(25)
print(t.celsius)     # 25    — looks like attribute access
print(t.fahrenheit)  # 77.0
print(t.kelvin)      # 298.15

t.celsius = 100      # uses setter
print(t.fahrenheit)  # 212.0

# t.celsius = -300   # ValueError!

Real-World Encapsulation Example

Bank Account
class BankAccount:
    def __init__(self, owner: str, initial_balance: float = 0):
        self.__owner = owner
        self.__balance = initial_balance
        self.__transactions = []

    @property
    def owner(self) -> str:
        return self.__owner

    @property
    def balance(self) -> float:
        return self.__balance

    def deposit(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        self.__balance += amount
        self.__transactions.append(f"+${amount:.2f}")

    def withdraw(self, amount: float) -> None:
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive")
        if amount > self.__balance:
            raise ValueError("Insufficient funds")
        self.__balance -= amount
        self.__transactions.append(f"-${amount:.2f}")

    def get_statement(self) -> str:
        history = "\n".join(self.__transactions) or "No transactions"
        return f"Account: {self.__owner}\nBalance: ${self.__balance:.2f}\n{history}"

acc = BankAccount("Alice", 500)
acc.deposit(200)
acc.withdraw(100)
print(acc.get_statement())
# Account: Alice
# Balance: $600.00
# +$200.00
# -$100.00

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.