Python tutorials  /  Encapsulation in Python
Chapter 16 · Python

Encapsulation in Python

Encapsulation is an Object-Oriented Programming concept that involves bundling data (attributes) and the methods that operate on that data into a single unit called a class, while restricting direct access to some of the object's components to protect the integrity of the data.

Encapsulation means wrapping variables and functions together inside a class and controlling who can see or change those variables from outside the class. Instead of letting any part of your program directly modify an object's data, you provide controlled access through methods, so the internal details stay hidden and safe from accidental misuse.

Think of an ATM machine. As a user, you interact with buttons like 'Check Balance', 'Withdraw Cash', and 'Deposit Cash', but you never directly access the bank's database or the machine's internal cash storage mechanism. The internal logic is hidden from you, and you can only perform actions through the exposed interface. Similarly, in a Python banking application, a BankAccount class hides the actual balance variable using a private attribute, and only allows you to check or change it through methods like deposit() and withdraw(), which include validation checks such as preventing a negative balance or unauthorized withdrawal.

Encapsulation is needed to protect an object's internal state from accidental or unauthorized modification, which helps maintain data integrity and consistency. It allows developers to change the internal implementation of a class without affecting the code that uses the class, as long as the public interface remains the same. It also improves security by hiding sensitive data, reduces complexity for the user of a class by exposing only relevant methods, and makes debugging easier since data changes are controlled through a limited set of methods rather than scattered throughout the codebase.

  • Public Members: Attributes and methods that have no underscore prefix and are accessible from anywhere, both inside and outside the class. By default, all members in Python are public unless explicitly marked otherwise.
  • Protected Members: Attributes and methods prefixed with a single underscore (_variable) are considered protected by convention. They signal to other developers that these members are intended for internal use within the class and its subclasses, though Python does not strictly enforce this restriction.
  • Private Members: Attributes and methods prefixed with double underscores (__variable) trigger Python's name mangling, making them harder to access directly from outside the class. This provides the strongest form of access restriction available in Python.
  • Encapsulation via Getters and Setters: Controlled access to private or protected attributes is provided through getter and setter methods, or more idiomatically in Python, through the @property decorator, allowing validation logic to run whenever a value is read or modified.
class ClassName: def __init__(self, value): self.public_var = value # Public self._protected_var = value # Protected (convention) self.__private_var = value # Private (name mangled) @property def private_var(self): return self.__private_var @private_var.setter def private_var(self, new_value): if new_value > 0: self.__private_var = new_value else: raise ValueError("Value must be positive")
Suppose you are building a BankAccount class where the balance should never be directly modifiable from outside the class, since that could allow a bug or malicious code to set a negative balance or bypass transaction rules. You need a way to store the balance internally while only allowing changes through controlled methods like deposit() and withdraw() that enforce business rules such as 'withdrawal amount cannot exceed available balance'. Encapsulation solves this by hiding the balance attribute and exposing only safe, validated methods to interact with it.
Basic Encapsulation with Private Attributes
Demonstrates hiding a bank account's balance using a private attribute and exposing controlled methods to deposit and withdraw money.
Python
class BankAccount: def __init__(self, owner, balance=0): self.owner = owner self.__balance = balance # private attribute 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 balance") elif amount <= 0: print("Withdrawal amount must be positive") else: self.__balance -= amount print(f"Withdrew {amount}. New balance: {self.__balance}") def get_balance(self): return self.__balance account = BankAccount("Rahul", 1000) account.deposit(500) account.withdraw(300) print("Final Balance:", account.get_balance())
Deposited 500. New balance: 1500 Withdrew 300. New balance: 1200 Final Balance: 1200
The __balance attribute is private and cannot be accessed directly from outside the class. All modifications happen through deposit() and withdraw(), which validate the amount before changing the balance, protecting the object's internal state.
Demonstrating Name Mangling of Private Attributes
Shows what happens when you try to access a private attribute directly and how Python's name mangling actually works internally.
Python
class Employee: def __init__(self, name, salary): self.name = name self.__salary = salary # private attribute emp = Employee("Anita", 50000) try: print(emp.__salary) except AttributeError as e: print("Error:", e) # Accessing via name mangling (not recommended) print("Accessed via name mangling:", emp._Employee__salary)
Error: 'Employee' object has no attribute '__salary' Accessed via name mangling: 50000
Directly accessing emp.__salary raises an AttributeError because Python internally renames the attribute to _Employee__salary (name mangling). While it can still technically be accessed using the mangled name, this is not recommended and defeats the purpose of encapsulation.
Encapsulation Using @property Decorator
Demonstrates the Pythonic way of implementing getters and setters using the @property decorator to validate data before assignment.
Python
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, new_price): if new_price < 0: raise ValueError("Price cannot be negative") self.__price = new_price laptop = Product("Laptop", 55000) print("Initial price:", laptop.price) laptop.price = 60000 print("Updated price:", laptop.price) try: laptop.price = -500 except ValueError as e: print("Error:", e)
Initial price: 55000 Updated price: 60000 Error: Price cannot be negative
The @property decorator turns the price() method into a getter, and @price.setter turns another method into a setter, allowing laptop.price to be accessed and assigned like a normal attribute while still running validation logic behind the scenes.
Protected Members and Inheritance
Shows how protected attributes (single underscore) are conventionally accessible within a subclass, unlike private attributes.
Python
class Vehicle: def __init__(self, brand, speed): self.brand = brand self._speed = speed # protected attribute class Car(Vehicle): def display_speed(self): print(f"{self.brand} speed: {self._speed} km/h") car = Car("Toyota", 180) car.display_speed() print("Accessed directly:", car._speed)
Toyota speed: 180 km/h Accessed directly: 180
The _speed attribute is protected by convention, meaning it is intended for internal use by the class and its subclasses. Unlike private attributes, Python does not enforce strict restrictions on protected members, so it can still be accessed directly, though it is discouraged in good coding practice.
  • Assuming Double Underscore Attributes Are Completely Inaccessible: Beginners often think that private attributes (with double underscores) are fully hidden and secure. In reality, Python only performs name mangling (renaming to _ClassName__attribute), and the attribute can still be accessed if someone knows the mangled name, making it a convention for discouraging access rather than true security.
  • Overusing Getters and Setters Unnecessarily: Developers coming from Java often write explicit get_x() and set_x() methods for every single attribute, even when no validation or extra logic is needed. This leads to verbose, non-Pythonic code. Python's @property decorator should be used only when you actually need to add logic during access or modification.
  • Directly Modifying 'Protected' Attributes from Outside the Class: Since Python does not enforce restrictions on single-underscore protected attributes, developers often access and modify them directly from outside the class hierarchy, defeating the purpose of encapsulation and creating tightly coupled, fragile code.
  • Forgetting That Name Mangling Applies Differently in Subclasses: When a private attribute is defined in a parent class, subclasses cannot access it directly by its original name because Python mangles it based on the class where it was defined, not the subclass. This confuses developers who expect private attributes to behave like protected attributes during inheritance.
  • Exposing Mutable Private Attributes Through Getters Without Copying: Returning a mutable private attribute (like a list or dictionary) directly from a getter method allows external code to modify the internal state indirectly, even though the attribute itself is private, breaking encapsulation. Returning a copy of the mutable object prevents this issue.
  • Use Single Underscore for Internal Use, Double Underscore for Strict Restriction: Use a single underscore prefix (_variable) to indicate an attribute is intended for internal use within the class and its subclasses. Reserve the double underscore prefix (__variable) for attributes that truly need strong protection from accidental access or name clashes in subclasses.
  • Prefer @property Over Traditional Getter and Setter Methods: Use Python's @property and @x.setter decorators instead of writing separate get_x() and set_x() methods. This keeps the syntax clean, allowing attribute-like access (obj.value) while still enabling validation and logic behind the scenes.
  • Validate Data Inside Setters: Always add validation logic inside setter methods (or property setters) to ensure that only valid data is assigned to an attribute, such as checking for negative numbers, empty strings, or incorrect data types, preventing invalid object states.
  • Return Copies of Mutable Objects from Getters: When a getter method returns a mutable object like a list or dictionary, return a copy (using .copy() or list()/dict() constructors) instead of the original reference, to prevent external code from indirectly modifying the internal state of the object.
  • Document the Intended Access Level of Each Attribute: Since Python relies on naming conventions rather than strict enforcement, clearly document in docstrings or comments which attributes are meant to be public, protected, or private, so other developers working on the codebase understand the intended encapsulation boundaries.
What is encapsulation in Python, and why is it important?
Encapsulation is the OOP principle of bundling data and methods that operate on that data within a single class, while restricting direct access to some of the object's internal components. It is important because it protects data integrity by preventing unauthorized or accidental modification, allows internal implementation details to change without affecting external code, and improves security by hiding sensitive information behind controlled interfaces.
What is the difference between protected and private attributes in Python?
Protected attributes are prefixed with a single underscore (_variable) and are a naming convention indicating the attribute should only be used within the class and its subclasses, but Python does not enforce this restriction. Private attributes are prefixed with double underscores (__variable) and undergo name mangling, where Python internally renames them to _ClassName__variable, making accidental access from outside the class much less likely, though not impossible.
What is name mangling in Python, and how does it relate to encapsulation?
Name mangling is the mechanism Python uses for attributes prefixed with double underscores, where the interpreter internally renames the attribute to _ClassName__attribute. This makes it harder (though not impossible) to accidentally access or override private attributes from outside the class or from subclasses, supporting the goal of encapsulation by discouraging direct external access.
How does the @property decorator support encapsulation in Python?
The @property decorator allows a method to be accessed like an attribute, enabling you to hide the internal implementation of an attribute behind a getter method while still allowing simple, attribute-style access syntax (obj.value). Combined with @value.setter, it allows validation logic to run whenever the attribute is assigned a new value, enforcing controlled access without exposing the raw internal variable directly.
Can encapsulation be considered a security feature in Python? Why or why not?
Encapsulation in Python is primarily a convention-based mechanism for organizing code and preventing accidental misuse, rather than a strict security feature. Since private attributes can still be accessed through name mangling (e.g., obj._ClassName__attribute), encapsulation does not provide true security against a determined developer, but it does effectively prevent accidental modification and clearly signals intended usage boundaries within a codebase.
What happens if you try to access a double-underscore private attribute directly from outside the class?
Python raises an AttributeError because the private attribute has been renamed internally through name mangling (e.g., __balance becomes _ClassName__balance). The original attribute name no longer exists as-is in the object's namespace, so direct access using the original name fails unless you use the mangled name explicitly.
Create a class 'Student' with a private attribute '__marks'. Add a setter method that only allows marks between 0 and 100, and a getter method to retrieve the marks. Test the class by trying to set both a valid and an invalid mark.
class Student: def __init__(self, name): self.name = name self.__marks = 0 def set_marks(self, marks): if 0 <= marks <= 100: self.__marks = marks else: print("Invalid marks. Must be between 0 and 100") def get_marks(self): return self.__marks student = Student("Riya") student.set_marks(85) print("Marks:", student.get_marks()) student.set_marks(150) print("Marks:", student.get_marks())
Refactor the 'Student' class from the previous question to use the @property decorator instead of explicit getter and setter methods, while keeping the same validation logic.
class Student: def __init__(self, name): self.name = name self.__marks = 0 @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("Riya") student.marks = 90 print("Marks:", student.marks) try: student.marks = -10 except ValueError as e: print("Error:", e)
Create a class 'Inventory' that stores a private list of items. Provide methods to add an item, remove an item, and view all items, ensuring the internal list cannot be directly modified from outside the class.
class Inventory: def __init__(self): self.__items = [] def add_item(self, item): self.__items.append(item) print(f"{item} added to inventory") def remove_item(self, item): if item in self.__items: self.__items.remove(item) print(f"{item} removed from inventory") else: print(f"{item} not found in inventory") def view_items(self): return list(self.__items) # returns a copy inv = Inventory() inv.add_item("Laptop") inv.add_item("Mouse") inv.remove_item("Mouse") print("Current Inventory:", inv.view_items())

Encapsulation in Python is the practice of bundling data and methods within a class while restricting direct access to internal attributes through naming conventions like single underscore (protected) and double underscore (private, with name mangling). It enables controlled access via getter and setter methods or the @property decorator, allowing validation logic to run during data access or modification. Encapsulation improves data integrity, security, and maintainability by hiding implementation details and exposing only a clean, controlled interface to the outside world.

© 2026 CompileX. Maintained by Aditya Kumar Sharma.