Python Encapsulation
Learn how to protect an object's internal data and control how that data can be accessed or modified. Encapsulation is an important object-oriented programming principle for building safer, cleaner, and more maintainable Python applications.
Learning Objectives
By the end of this lesson, you will understand how encapsulation helps control access to data and behavior inside Python classes.
You will learn how to:
- Understand the concept of encapsulation.
- Distinguish between public, protected, and private members.
- Understand Python's underscore conventions.
- Understand name mangling.
- Create controlled access to object data.
- Use getters and setters.
- Use the
property()function. - Use the
@propertydecorator. - Validate data before storing it.
- Build safer object-oriented applications.
What Is Encapsulation?
Encapsulation is the practice of keeping data and the methods that operate on that data together inside a class while controlling how the internal data is accessed.
In simple terms, encapsulation means:
Imagine a bank account. The account has an internal balance. You should not allow every part of the program to freely change that balance.
Instead, the account can provide methods such as:
deposit()
withdraw()
get_balance()
These methods can apply rules before changing or returning the account's internal state.
Why Encapsulation Matters
Without proper control over object data, other parts of a program may accidentally put objects into invalid states.
Consider this simple class:
class BankAccount:
def __init__(self, balance):
self.balance = balance
account = BankAccount(5000)
account.balance = -100000
print(account.balance)
-100000
The program allowed the balance to be changed directly, even though a negative balance may not be valid for the application's business rules.
Encapsulation lets us create controlled access and validation.
Classes and Data
When we create an object, its attributes represent the object's state.
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
student = Student("Ali", 20)
print(student.name)
print(student.age)
Ali
20
Both attributes are directly accessible because they are public attributes.
Python provides naming conventions that help developers communicate how attributes should be accessed.
Public Members
A normal attribute or method is considered public.
Public members can normally be accessed from outside the class.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
product = Product("Laptop", 120000)
print(product.name)
print(product.price)
Here, both name and price are
public attributes.
They can also be changed directly:
product.price = 110000
Protected Members
Python uses a single underscore before a name to indicate that a member is intended for internal or subclass use.
Example:
class Employee:
def __init__(self, name, salary):
self.name = name
self._salary = salary
employee = Employee("Ali", 80000)
print(employee.name)
print(employee._salary)
Python does not technically prevent access to
_salary.
The underscore is a convention telling developers:
This is different from languages that enforce protected access through strict access modifiers.
Private Members
Python uses a double underscore at the beginning of a name to trigger name mangling.
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def show_balance(self):
print(self.__balance)
account = BankAccount(5000)
account.show_balance()
5000
The double underscore changes how Python stores the attribute name internally.
Direct access like this:
account.__balance
will not work normally.
Understanding Name Mangling
When Python encounters an attribute such as:
self.__balance
inside a class named BankAccount, Python
internally transforms the name approximately into:
_BankAccount__balance
This process is called name mangling.
class BankAccount:
def __init__(self, balance):
self.__balance = balance
account = BankAccount(5000)
print(account._BankAccount__balance)
5000
Private Methods
Methods can also use the double underscore convention.
class Order:
def __calculate_tax(self, amount):
return amount * 0.10
def final_price(self, amount):
tax = self.__calculate_tax(amount)
return amount + tax
order = Order()
print(order.final_price(1000))
1100.0
The internal tax calculation is hidden behind the
public-facing final_price() method.
This makes the class easier to use because users of the class do not need to understand every internal calculation.
Getters
A getter is a method used to retrieve the value of an internal attribute.
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def get_balance(self):
return self.__balance
account = BankAccount(5000)
print(account.get_balance())
5000
Instead of exposing the internal attribute directly, the class controls how the balance is retrieved.
Setters
A setter is a method used to modify an internal value while applying rules or validation.
class Student:
def __init__(self, marks):
self.__marks = marks
def get_marks(self):
return self.__marks
def set_marks(self, marks):
if 0 <= marks <= 100:
self.__marks = marks
else:
print("Marks must be between 0 and 100.")
student = Student(85)
student.set_marks(92)
print(student.get_marks())
student.set_marks(150)
92
Marks must be between 0 and 100.
The setter prevents invalid values from entering the object's state.
The property() Function
Python provides the built-in property()
function to create controlled attribute access.
A basic property can connect a getter and setter to an attribute-like interface.
class Student:
def __init__(self, marks):
self._marks = marks
def get_marks(self):
return self._marks
def set_marks(self, marks):
if 0 <= marks <= 100:
self._marks = marks
else:
raise ValueError("Marks must be between 0 and 100.")
marks = property(get_marks, set_marks)
student = Student(80)
print(student.marks)
student.marks = 95
print(student.marks)
80
95
Notice that we use:
student.marks
instead of:
student.get_marks()
This provides a cleaner interface.
The @property Decorator
In modern Python code, the @property decorator
is usually more readable than manually creating a
property().
class Student:
def __init__(self, marks):
self._marks = marks
@property
def marks(self):
return self._marks
@marks.setter
def marks(self, value):
if 0 <= value <= 100:
self._marks = value
else:
raise ValueError(
"Marks must be between 0 and 100."
)
student = Student(85)
print(student.marks)
student.marks = 95
print(student.marks)
85
95
The getter is defined using:
@property
The setter is connected using:
@marks.setter
Data Validation
One of the most useful applications of encapsulation is validation.
Suppose an e-commerce product must have a positive price.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError(
"Price cannot be negative."
)
self._price = value
product = Product("Laptop", 120000)
print(product.price)
product.price = 110000
print(product.price)
120000
110000
Now try:
product.price = -5000
The setter raises a ValueError instead of
allowing an invalid price.
Read-Only Properties
A property does not always need a setter.
If you define only a getter, the property can effectively become read-only through the normal public interface.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def area(self):
return 3.14159 * self._radius ** 2
circle = Circle(5)
print(circle.area)
78.53975
There is no setter for area because the area is
calculated from the radius.
A user can read:
circle.area
but should not directly assign a new area.
Real-World Examples
Encapsulation appears naturally in many real-world software systems.
Banking Applications
A bank account can protect its balance and expose controlled operations such as:
deposit()withdraw()get_balance()
E-commerce Systems
A product can validate its price, stock quantity, discount, and other business rules.
User Accounts
Internal account information can be managed through methods instead of allowing unrestricted changes.
Employee Systems
Salary, employee status, and other attributes may require validation before being changed.
Inventory Systems
Stock quantities can be protected so that an order cannot accidentally reduce stock below an allowed value.
Benefits and Limitations
Benefits
- Protects object state from accidental modification.
- Allows validation before changing data.
- Keeps internal implementation details organized.
- Makes classes easier to maintain.
- Provides a cleaner public interface.
- Allows internal implementation to change without changing how users interact with the class.
Limitations
- Too many getters and setters can create unnecessary complexity.
- Python's access control is based heavily on conventions.
- Name mangling is not a security mechanism.
- Overengineering simple classes can make code harder to read.
Common Mistakes
1. Thinking a Single Underscore Creates True Privacy
A single underscore is mainly a convention.
self._salary
can still technically be accessed from outside the class.
2. Treating Double Underscore as Security
Name mangling does not encrypt or securely hide data.
3. Creating Getters and Setters for Everything
Not every attribute needs a getter and setter. If direct access is safe and clear, a public attribute may be better.
4. Forgetting Validation
A setter becomes much more useful when it protects the object's state.
5. Raising the Wrong Exception
Use an appropriate exception such as
ValueError when a value is invalid.
6. Confusing Encapsulation with Abstraction
Encapsulation focuses on controlling access to internal state and implementation details. Abstraction focuses on exposing essential behavior while hiding unnecessary complexity.
Practice Exercises
Try these exercises yourself before moving to the challenge.
Exercise 1 — Bank Account
Create a BankAccount class with a private
__balance attribute and a
get_balance() method.
Exercise 2 — Student Marks
Create a private marks attribute and a setter that only accepts values from 0 to 100.
Exercise 3 — Product Price
Create a product class where negative prices are rejected.
Exercise 4 — Protected Attribute
Create an employee class using _salary as a
protected-by-convention attribute.
Exercise 5 — Private Method
Create a private method that calculates tax and call it from a public method.
Exercise 6 — Property
Rewrite a getter and setter using the
@property decorator.
Exercise 7 — Read-Only Property
Create a class with a calculated property that has a getter but no setter.
Exercise 8 — Inventory
Create an inventory class that prevents stock from becoming negative.
Mini Challenge — Secure Product System
Your Mission
Build an e-commerce product class that uses encapsulation to protect price and stock data.
Requirements
-
Create a class called
Product. - Store the product name.
- Store price internally.
- Store stock internally.
-
Create a property called
price. - Prevent negative prices.
-
Create a property called
stock. - Prevent negative stock.
-
Create a method called
sell(quantity). - Prevent customers from buying more items than available.
class Product:
def __init__(self, name, price, stock):
self.name = name
self.price = price
self.stock = stock
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError(
"Price cannot be negative."
)
self._price = value
@property
def stock(self):
return self._stock
@stock.setter
def stock(self, value):
if value < 0:
raise ValueError(
"Stock cannot be negative."
)
self._stock = value
def sell(self, quantity):
if quantity <= 0:
raise ValueError(
"Quantity must be positive."
)
if quantity > self.stock:
raise ValueError(
"Not enough stock available."
)
self.stock -= quantity
product = Product("Laptop", 120000, 10)
print(product.price)
print(product.stock)
product.sell(2)
print(product.stock)
120000
10
8
final_price property.
Quick Knowledge Check
Test your understanding of encapsulation.
1. What is encapsulation?
2. Which naming style represents a protected-by-convention member?
3. Which naming style triggers name mangling?
4. What is a getter commonly used for?
5. What is a setter commonly used for?
6. Which decorator creates a property getter?
7. Which decorator is used to define a setter for a property called price?
8. Is Python's double underscore mechanism a security feature?
9. Why might a setter validate data?
10. What is a read-only property?
Lesson Summary
Encapsulation is an important principle of object-oriented programming that helps control how an object's internal state is accessed and modified.
- Encapsulation controls access to internal data and behavior.
- Normal attributes and methods are generally considered public.
-
A single underscore such as
_valueindicates an internal/protected-by-convention member. -
A double underscore such as
__valuetriggers name mangling. - Name mangling is not a security mechanism.
- Getters retrieve controlled data.
- Setters can validate changes before storing new values.
-
The
property()function creates controlled attribute access. -
The
@propertydecorator provides a clean modern syntax. - Properties can be used to create read-only values.
- Encapsulation is especially useful for enforcing business rules in real applications.
Ready to move forward?
Mark this lesson complete after you understand public, protected, and private members, getters, setters, properties, and data validation.