Home Course Roadmap
Home Python Course Lesson 47
Lesson 47 · Object-Oriented Programming 45–55 minutes Intermediate

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.

Object-Oriented Programming
0%
01

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 @property decorator.
  • Validate data before storing it.
  • Build safer object-oriented applications.
02

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:

Protect the internal state of an object and provide controlled ways to interact with it.

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.

03

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:

Uncontrolled Data
class BankAccount:

    def __init__(self, balance):
        self.balance = balance


account = BankAccount(5000)

account.balance = -100000

print(account.balance)
Output
-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.

04

Classes and Data

When we create an object, its attributes represent the object's state.

Object State
class Student:

    def __init__(self, name, age):
        self.name = name
        self.age = age


student = Student("Ali", 20)

print(student.name)
print(student.age)
Output
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.

05

Public Members

A normal attribute or method is considered public.

Public members can normally be accessed from outside the class.

Public Attribute
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
Public does not mean "bad." Public attributes are perfectly appropriate when direct access is safe and intentional.
06

Protected Members

Python uses a single underscore before a name to indicate that a member is intended for internal or subclass use.

Example:

Protected Convention
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 member is intended for internal use."

This is different from languages that enforce protected access through strict access modifiers.

07

Private Members

Python uses a double underscore at the beginning of a name to trigger name mangling.

Private Attribute
class BankAccount:

    def __init__(self, balance):
        self.__balance = balance

    def show_balance(self):
        print(self.__balance)


account = BankAccount(5000)

account.show_balance()
Output
5000

The double underscore changes how Python stores the attribute name internally.

Direct access like this:

account.__balance

will not work normally.

Python's "private" mechanism is not absolute security. It is primarily a mechanism for avoiding accidental access and name collisions.
08

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.

Name Mangling Example
class BankAccount:

    def __init__(self, balance):
        self.__balance = balance


account = BankAccount(5000)

print(account._BankAccount__balance)
Output
5000
Do not treat name mangling as a security feature. Someone who understands Python can still access the mangled name. Its main purpose is to discourage accidental access and prevent naming conflicts.
09

Private Methods

Methods can also use the double underscore convention.

Private Method
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))
Output
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.

10

Getters

A getter is a method used to retrieve the value of an internal attribute.

Getter Example
class BankAccount:

    def __init__(self, balance):
        self.__balance = balance

    def get_balance(self):
        return self.__balance


account = BankAccount(5000)

print(account.get_balance())
Output
5000

Instead of exposing the internal attribute directly, the class controls how the balance is retrieved.

11

Setters

A setter is a method used to modify an internal value while applying rules or validation.

Getter and Setter
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)
Output
92
Marks must be between 0 and 100.

The setter prevents invalid values from entering the object's state.

12

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.

Using property()
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)
Output
80
95

Notice that we use:

student.marks

instead of:

student.get_marks()

This provides a cleaner interface.

13

The @property Decorator

In modern Python code, the @property decorator is usually more readable than manually creating a property().

Modern Property Syntax
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)
Output
85
95

The getter is defined using:

@property

The setter is connected using:

@marks.setter
Professional Python pattern: Properties allow you to expose a clean attribute-like interface while keeping validation or internal logic behind the scenes.
14

Data Validation

One of the most useful applications of encapsulation is validation.

Suppose an e-commerce product must have a positive price.

Product Price Validation
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)
Output
120000
110000

Now try:

product.price = -5000

The setter raises a ValueError instead of allowing an invalid price.

Encapsulation allows business rules to live close to the data they protect.
15

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.

Read-Only Property
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)
Output
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.

16

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.

Practical principle: Put important business rules close to the data they protect instead of scattering those rules throughout the application.
17

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.
Remember: Encapsulation is about good design and controlled interaction, not about making every attribute private.
18

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.

19

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.

20

Mini Challenge — Secure Product System

Your Mission

Build an e-commerce product class that uses encapsulation to protect price and stock data.

Requirements

  1. Create a class called Product.
  2. Store the product name.
  3. Store price internally.
  4. Store stock internally.
  5. Create a property called price.
  6. Prevent negative prices.
  7. Create a property called stock.
  8. Prevent negative stock.
  9. Create a method called sell(quantity).
  10. Prevent customers from buying more items than available.
Starter Structure
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)
Expected Output
120000
10
8
Extension Challenge: Add a discount property that only accepts values from 0 to 100. Then create a calculated final_price property.
21

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?

22

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 _value indicates an internal/protected-by-convention member.
  • A double underscore such as __value triggers 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 @property decorator 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.
Key takeaway: Good encapsulation lets an object protect its internal state while providing a clean and controlled interface for the rest of the program.

Ready to move forward?

Mark this lesson complete after you understand public, protected, and private members, getters, setters, properties, and data validation.