Encapsulation is one of the core principles of Object-Oriented Programming (OOP) in Python. Encapsulation is used to bundle data (attributes) and methods that operate on the data into a single unit, usually a class, and restrict direct access to some components. This tutorial covers everything you need to know about encapsulation in Python, including its purpose, syntax, access modifiers, and real-world examples.
In Python, encapsulation is the practice of bundling data and the methods that operate on the data within a single unit, typically a class. Encapsulation helps protect the data from being accessed directly by external code, allowing controlled access to the internal state of an object. This is done by using access modifiers to limit the visibility of attributes and methods.
Encapsulation provides several benefits:
Python uses naming conventions to define access levels for attributes and methods. Although Python doesn’t have strict access modifiers like private
or protected
in other languages, it provides the following conventions:
Public members are accessible from anywhere, inside or outside of the class. By default, all attributes and methods in Python are public.
class Car:
def __init__(self, make, model):
self.make = make # Public attribute
self.model = model # Public attribute
def display_info(self): # Public method
print(f"Car make: {self.make}, Model: {self.model}")
my_car = Car("Toyota", "Corolla")
print(my_car.make) # Output: Toyota
print(my_car.model) # Output: Corolla
my_car.display_info() # Output: Car make: Toyota, Model: Corolla
Protected members are indicated by a single underscore _
prefix. Although still accessible outside the class, the underscore is a convention to signal that these members are intended for internal use only and should not be accessed directly.
class Car:
def __init__(self, make, model, year):
self._make = make # Protected attribute
self._model = model # Protected attribute
self._year = year # Protected attribute
def _display_info(self): # Protected method
print(f"{self._make} {self._model} ({self._year})")
my_car = Car("Honda", "Civic", 2022)
my_car._display_info() # Output: Honda Civic (2022)
Private members are indicated by a double underscore __
prefix. These attributes and methods are name-mangled by Python to prevent direct access from outside the class.
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private attribute
def deposit(self, amount):
self.__balance += amount
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
return amount
else:
print("Insufficient funds")
def get_balance(self):
return self.__balance
account = BankAccount(1000)
account.deposit(200)
print(account.get_balance()) # Output: 1200
__balance
, which is accessed only through methods within the class.Getters and setters are methods used to access and modify private attributes while preserving data integrity.
class Student:
def __init__(self, name, age):
self.__name = name # Private attribute
self.__age = age # Private attribute
# Getter for name
def get_name(self):
return self.__name
# Setter for name
def set_name(self, name):
self.__name = name
# Getter for age
def get_age(self):
return self.__age
# Setter for age
def set_age(self, age):
if age > 0:
self.__age = age
else:
print("Invalid age")
student = Student("Alice", 20)
print(student.get_name()) # Output: Alice
student.set_age(21)
print(student.get_age()) # Output: 21
get_name()
and set_name()
provide controlled access to the __name
attribute.get_age()
and set_age()
control access to __age
, enforcing constraints on the age value.Encapsulation provides several advantages:
Let’s use encapsulation to create a bank account system where a user can deposit, withdraw, and check the balance.
class BankAccount:
def __init__(self, account_holder, balance=0):
self.__account_holder = account_holder # Private attribute
self.__balance = balance # Private attribute
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"Deposited: {amount}")
else:
print("Deposit amount must be positive")
def withdraw(self, amount):
if amount > self.__balance:
print("Insufficient funds")
else:
self.__balance -= amount
print(f"Withdrawn: {amount}")
def get_balance(self):
return self.__balance
account = BankAccount("John Doe", 500)
account.deposit(200) # Output: Deposited: 200
account.withdraw(100) # Output: Withdrawn: 100
print(account.get_balance()) # Output: 600
Encapsulation is useful for managing employee data, ensuring that sensitive data (like salary) is protected.
class Employee:
def __init__(self, name, position, salary):
self.name = name # Public attribute
self.position = position # Public attribute
self.__salary = salary # Private attribute
def get_salary(self):
return self.__salary
def set_salary(self, salary):
if salary > 0:
self.__salary = salary
else:
print("Invalid salary")
# Creating an employee
emp = Employee("Alice", "Manager", 70000)
print(emp.get_salary()) # Output: 70000
emp.set_salary(75000)
print(emp.get_salary()) # Output: 75000
__salary
attribute is private, so it is only accessible through get_salary()
and set_salary()
methods.Encapsulation is a crucial aspect of Object-Oriented Programming in Python, allowing developers to control access to data within a class. By using access modifiers, we can protect sensitive information and enforce constraints on how data is modified. Getters and setters offer a way to control data access while maintaining data integrity.
By mastering encapsulation, you can: