Python Encapsulation

Encapsulation is a fundamental principle in object-oriented programming (OOP) that restricts access to the internal state and behavior of an object. In Python, encapsulation is implemented by defining attributes and methods that can be accessed only within the class, providing control over how the data is modified or accessed.

Key Concepts of Encapsulation in Python

Private and Public Access Modifiers:

Public Attributes: Accessible from anywhere. In Python, any variable or method not prefixed with an underscore is public by default.

Private Attributes: Intended to be inaccessible from outside the class, although Python only provides a convention for indicating this (not strict enforcement).

  • Prefixing an attribute or method name with a single underscore (e.g., _attribute) suggests it should be treated as non-public.
  • Prefixing with a double underscore (e.g., __attribute) causes name mangling, making it harder to access from outside the class.

Getters and Setters:

These methods control access to private attributes by providing indirect ways to read or modify the value.

Useful for adding validation or logic when data is set or retrieved.

Encapsulation Using Properties:

In Python, the @property decorator can be used to make getters and setters more Pythonic. Properties allow attributes to be accessed like regular variables but managed by getters and setters in the background.

Example:


class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner                # Public attribute
        self.__balance = balance           # Private attribute using double underscore

    @property
    def balance(self):
        """Getter for balance."""
        return self.__balance

    @balance.setter
    def balance(self, amount):
        """Setter for balance with validation."""
        if amount < 0:
            print("Invalid amount! Balance cannot be negative.")
        else:
            self.__balance = amount

    def deposit(self, amount):
        """Method to deposit money into account."""
        if amount > 0:
            self.__balance += amount
            print(f"Deposited ${amount}. New balance is ${self.__balance}")
        else:
            print("Deposit amount must be positive.")

    def withdraw(self, amount):
        """Method to withdraw money from account with validation."""
        if 0 < amount <= self.__balance:
            self.__balance -= amount
            print(f"Withdrew ${amount}. New balance is ${self.__balance}")
        else:
            print("Invalid withdrawal amount or insufficient funds.")

# Usage
account = BankAccount("John")
account.deposit(200)
account.withdraw(50)
print(account.balance)         # Accessing balance via the getter method
account.balance = -10          # Attempting to set balance to a negative value

Explanation

Private Attribute __balance: The balance is private to prevent direct modifications from outside the class.

@property and @balance.setter: The balance property allows controlled access to the private attribute __balance. When attempting to set a negative balance, a warning is shown.

Public Methods (deposit and withdraw): These methods encapsulate logic for modifying the balance, validating input, and maintaining account integrity.

Encapsulation helps to hide internal implementation details and protect data integrity by controlling access and ensuring data consistency.