Encapsulation In Python
Python Encapsulation: Comprehensive Theory
Encapsulation is one of the fundamental concepts in object-oriented programming (OOP). It describes the idea of wrapping data and the methods that work on data within one unit, such as a class.
This concept is often used to hide the internal state of an object from the outside. This is called Information Hiding.
1. Why Use Encapsulation?
- Security: It keeps the data safe from accidental modification.
- Control: You can decide which data should be accessible and which should be hidden.
- Flexibility: You can change the internal implementation without affecting the code that uses the class.
2. Access Modifiers in Python
Python does not have keywords like private, protected, and public to enforce access control. Instead, it uses a naming convention.
Public Members
Public members are accessible from anywhere outside the class. All members in a Python class are public by default.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
emp = Employee("John", 5000)
print(emp.name) # Output: John
Protected Members (Convention)
Protected members are accessible within the class and its subclasses. We use a single underscore _ prefix to denote a protected member.
class Employee:
def __init__(self, name, salary):
self._name = name # Protected member
self._salary = salary
emp = Employee("John", 5000)
print(emp._name) # Output: John (But it shouldn't be accessed like this)
Note: This is just a convention and doesn't actually prevent access.
Private Members
Private members are accessible only within the class. We use a double underscore __ prefix to denote a private member.
class Employee:
def __init__(self, name, salary):
self.__salary = salary # Private member
emp = Employee("John", 5000)
# print(emp.__salary) # This will raise an AttributeError
3. Name Mangling
When you use a double underscore prefix, Python performs "name mangling". This means it changes the internal name of the variable to _ClassName__variableName to avoid naming conflicts in subclasses.
You can still access it (though you shouldn't):
print(emp._Employee__salary) # Output: 5000
@property
In Python, the most "Pythonic" way to handle encapsulation is using the @property decorator. This allows you to define a method that can be accessed like an attribute.
class Employee:
def __init__(self, name, salary):
self.__name = name
self.__salary = salary
@property
def salary(self):
"""Getter for salary"""
return self.__salary
@salary.setter
def salary(self, value):
"""Setter for salary with validation"""
if value > 0:
self.__salary = value
else:
print("Salary must be positive!")
emp = Employee("John", 5000)
print(emp.salary) # Accessing like an attribute (Getter)
emp.salary = 6000 # Assigning like an attribute (Setter)
print(emp.salary) # Output: 6000
5. Real-World Analogy
Think of a Bank Account. You can check your balance and deposit money, but you cannot directly change the balance variable in the bank's database. You have to use the bank's provided methods (the API), which ensure that all rules are followed.