What is encapsulation in Python
Encapsulation is one of the basic principles of object-oriented programming (OOP), which allows you to hide the internal implementation of an object and provide controlled access to its data through special methods. In Python, encapsulation is implemented using access modifiers and accessor methods.
Basics of encapsulation in Python
Python uses a double underscore __ before the attribute name to create private attributes. This makes attributes inaccessible to direct access from outside the class, ensuring data protection.
Example 1: Car class with encapsulation
class Car:
def __init__(self, make, model, year):
self.__make = make # Private attribute
self.__model = model
self.__year = year
def get_make(self):
return self.__make
def get_model(self):
return self.__model
def get_year(self):
return self.__year
def set_make(self, make):
self.__make = make
def set_model(self, model):
self.__model = model
def set_year(self, year):
if year > 1900: # Add validation
self.__year = year
else:
print("Incorrect release year")
def display_info(self):
print(f"Brand: {self.__make}, Model: {self.__model}, Year: {self.__year}")
# Using the class
car = Car("Toyota", "Camry", 2020)
car.display_info() # Output: Brand: Toyota, Model: Camry, Year: 2020
# Changing attributes via methods
car.set_year(2021)
car.display_info() # Output: Brand: Toyota, Model: Camry, Year: 2021
# Attempting to access attributes directly will cause an error
# print(car.__make) # AttributeError: 'Car' object has no attribute '__make'
Data validation encapsulation
One of the main advantages of encapsulation is the ability to add checks when object data changes.
Example 2: Employee class with validation
class Employee:
def __init__(self, name, age, salary):
self.__name = name
self.__age = age
self.__salary = salary
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def get_salary(self):
return self.__salary
def set_name(self, name):
if name and len(name.strip()) > 0:
self.__name = name.strip()
else:
print("Name cannot be empty")
def set_age(self, age):
if 18 <= age <= 65:
self.__age = age
else:
print("Age must be between 18 and 65 years old")
def set_salary(self, salary):
if salary > 0:
self.__salary = salary
else:
print("Salary should be positive")
def display_info(self):
print(f"Name: {self.__name}, Age: {self.__age}, Salary: {self.__salary} rub.")
# Creating and using
the employee object = Employee("Ivan", 30, 50,000)
employee.display_info() # Output: Name: Ivan, Age: 30, Salary: 50,000 rubles.
# Change via validated methods
employee.set_salary(60000)
employee.set_age(35)
employee.display_info() # Output: Name: Ivan, Age: 35, Salary: 60,000 rub.
Practical example: Bank account
Let's consider a more complex example with a bank account, where encapsulation is critically important for the security of transactions.
Example 3: BankAccount class
class BankAccount:
def __init__(self, account_number, initial_balance=0):
self.__account_number = account_number
self.__balance = initial_balance
self.__transaction_history = []
def get_account_number(self):
return self.__account_number
def get_balance(self):
return self.__balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
self.__transaction_history.append(f"Replenishment: +{amount}")
print(f"Account replenished by {amount}. Current balance: {self.__balance}")
else:
print("The deposit amount must be positive")
def withdraw(self, amount):
if amount <= 0:
print("Withdrawal amount must be positive")
elif amount > self.__balance:
print("Insufficient funds in the account")
else:
self.__balance -= amount
self.__transaction_history.append(f"Withdrawal: -{amount}")
print(f"Withdrawn {amount}. Current balance: {self.__balance}")
def get_transaction_history(self):
return self.__transaction_history.copy()
def display_info(self):
print(f"Account number: {self.__account_number}, Balance: {self.__balance} rub.")
# Using the class
account = BankAccount("12345678", 1000)
account.display_info() # Output: Account number: 12345678, Balance: 1000 rub.
# Operations with the account
account.deposit(500) # The account is topped up by 500. Current balance: 1,500
account.withdraw(300) # Withdrawn 300. Current balance: 1200
account.withdraw(2000) # Insufficient funds in the account
# View the history of operations
history = account.get_transaction_history()
for transaction in history:
print(transaction)
Encapsulation in the Student class
Example 4: Student class with extended validation
class Student:
def __init__(self, name, age, grade):
self.__name = name
self.__age = age
self.__grade = grade
self.__subjects = {}
def get_name(self):
return self.__name
def get_age(self):
return self.__age
def get_grade(self):
return self.__grade
def set_name(self, name):
if name and len(name.strip()) > 0:
self.__name = name.strip()
else:
print("Name cannot be empty")
def set_age(self, age):
if 16 <= age <= 25:
self.__age = age
else:
print ("The student's age must be between 16 and 25 years old")
def set_grade(self, grade):
if 0 <= grade <= 100:
self.__grade = grade
else:
print("The score should be from 0 to 100")
def add_subject(self, subject_name, score):
if 0 <= score <= 100:
self.__subjects[subject_name] = score
else:
print("Subject grade should be from 0 to 100")
def get_subjects(self):
return self.__subjects.copy()
def calculate_average(self):
if self.__subjects:
return sum(self.__subjects.values()) / len(self.__subjects)
return 0
def display_info(self):
print(f"Name: {self.__name}, Age: {self.__age}, Overall grade: {self.__grade}")
if self.__subjects:
print("Items:")
for subject, score in self.__subjects.items():
print(f"{subject}: {score}")
print(f"Average score: {self.calculate_average():.2f}")
# Creating and using
the student object = Student("Anna", 20, 90)
student.display_info()
# Adding subjects
student.add_subject("Mathematics", 95)
student.add_subject("Physics", 88)
student.add_subject("Chemistry", 92)
student.display_info()
# Changing the grade with validation
student.set_grade(95)
student.set_grade(105) # Incorrect value, will not change the grade
Advantages of encapsulation
- Data protection: Prevents accidental or incorrect modification of important attributes of an object
- Access control: Allows you to add checks and restrictions when changing data
- Modularity: Simplifies code maintenance and modification without affecting other parts of the program
- Security: Hides the internal implementation from external influences
Conclusion
Encapsulation in Python is a powerful tool for creating reliable and secure object-oriented programs. The use of private attributes and accessor methods allows you to control access to object data and ensures program integrity. Proper use of encapsulation makes the code more readable, secure, and easy to maintain.