Python Encapsulation
Table of Contents
In the last lesson, we learned about Python Polymorphism. After learning about polymorphism, let us learn about encapsulation in Python. Encapsulation is about keeping data and methods together, and hiding internal details.
In this tutorial, we will learn what encapsulation is, how to use it, and why it is useful.
๐งฉ What Is Encapsulation in Python?
Encapsulation is the practice of keeping data and methods together inside a class. And hiding the internal details from the outside.
Think of it like a car. We drive the car using the steering wheel, pedals, and gear stick. We do not need to know how the engine works. The engine is hidden. That is encapsulation.
In Python, encapsulation means:
- Keeping data (attributes) and methods together in a class
- Restricting direct access to some data
- Providing methods to access and modify data safely
๐ง Why Do We Need Encapsulation in Python?
Encapsulation protects data from accidental changes. If we allow direct access to all data, someone might set an invalid value. That could break our program.
Encapsulation also makes our code easier to maintain. We can change the internal details without affecting the outside code.
๐ Public, Protected, and Private Members in Python
Python does not have strict access control like some other languages. But it has conventions.
Public โ Accessible from anywhere. No underscore.
Protected โ Should only be accessed within the class and subclasses. One underscore _.
Private โ Should only be accessed within the class. Two underscores __.
Let me show you.
class Person: def __init__(self, name, age, salary): self.name = name # public self._age = age # protected self.__salary = salary # private
person = Person("Alex", 25, 50000)
print(person.name) # Worksprint(person._age) # Works, but should be avoidedprint(person.__salary) # Error!Output
Alex25AttributeError: 'Person' object has no attribute '__salary'The private attribute __salary cannot be accessed directly from outside. Python does name mangling. It changes the name internally to _Person__salary.
We can still access it if we know the mangled name. But we should not.
print(person._Person__salary) # Works, but bad practice๐ก How to Use Encapsulation in Python
We use encapsulation by making attributes private and providing methods to access them.
class Person: def __init__(self, name, age): self.name = name self.__age = age
def get_age(self): return self.__age
def set_age(self, age): if age > 0: self.__age = age else: print("Age cannot be negative")
person = Person("Alex", 25)
print(person.get_age())
person.set_age(30)print(person.get_age())
person.set_age(-5)print(person.get_age())Output
2530Age cannot be negative30The set_age method checks if the age is valid before setting it. That protects the data.
๐งช Getters and Setters in Python
Getters and setters are methods used to access and modify private attributes.
- Getter โ returns the value
- Setter โ sets the value, often with validation
class Student: def __init__(self, name, grade): self.name = name self.__grade = grade
def get_grade(self): return self.__grade
def set_grade(self, grade): if 0 <= grade <= 100: self.__grade = grade else: print("Grade must be between 0 and 100")
student = Student("Alex", 85)print(student.get_grade())
student.set_grade(95)print(student.get_grade())
student.set_grade(150)print(student.get_grade())Output
8595Grade must be between 0 and 10095That is encapsulation. The grade is protected. And we control how it is set.
๐ Using @property for Getters and Setters in Python
Python has a nicer way to do getters and setters. We use the @property decorator.
class Person: def __init__(self, name, age): self.name = name self._age = age
@property def age(self): return self._age
@age.setter def age(self, age): if age > 0: self._age = age else: print("Age cannot be negative")
person = Person("Alex", 25)
print(person.age)
person.age = 30print(person.age)
person.age = -5print(person.age)Output
2530Age cannot be negative30Now we can use person.age like a normal attribute. But it still goes through the getter and setter. That is clean and Pythonic.
๐ A Real-Life Example โ Bank Account
class BankAccount: def __init__(self, owner, balance): self.owner = owner self.__balance = balance
@property def balance(self): return self.__balance
def deposit(self, amount): if amount > 0: self.__balance += amount print(f"Deposited {amount}. New balance: {self.__balance}") else: print("Deposit amount must be positive")
def withdraw(self, amount): if amount > self.__balance: print("Insufficient funds") elif amount <= 0: print("Withdrawal amount must be positive") else: self.__balance -= amount print(f"Withdrew {amount}. New balance: {self.__balance}")
account = BankAccount("Alex", 1000)
print(account.balance)account.deposit(500)account.withdraw(200)account.withdraw(2000)Output
1000Deposited 500. New balance: 1500Withdrew 200. New balance: 1300Insufficient fundsThe balance is private. It can only be changed through deposit and withdraw methods. That is encapsulation.
โ๏ธ Common Mistakes with Encapsulation in Python
โ Mistake 1: Thinking private is truly private
person._Person__salary = 100000This works. Python does not enforce privacy. It is just a convention.
Fix: Respect the convention. Do not access private attributes from outside.
๐ซ Mistake 2: Using one underscore for private
self._age = age # protected, not privateOne underscore means protected. It can still be accessed from outside.
Fix: Use two underscores for private.
self.__age = age๐ Mistake 3: Forgetting to use the setter
class Person: def __init__(self, age): self._age = age
@property def age(self): return self._age
@age.setter def age(self, age): self._age = age
person = Person(25)person._age = -5 # bypasses the setterThis bypasses the validation. Fix: Always use the property.
person.age = -5Next, we will learn about Python Abstraction and hide complex details behind a simple interface.