Python

Python Basics

Data Structures in Python

Control Flow and Loops

Functions and Scope

Object-Oriented Programming (OOP)

Python Programs


🔐 Encapsulation and Data Hiding in Python (_protected, __private)

One of the key pillars of object-oriented programming (OOP) is encapsulation. If you’ve heard of the phrase “Don’t touch what you shouldn’t,” you already get the gist. Encapsulation helps protect the internal state of an object by restricting direct access to some of its attributes or methods.

In Python, encapsulation is achieved using access specifiers like _protected and __private, even though Python doesn’t enforce strict access control like languages such as Java or C++. Instead, Python relies on conventions and name mangling to manage access levels.

In this guide, we’ll explore:

  • What encapsulation is
  • The concept of data hiding
  • Python’s naming conventions (_ and __)
  • Practical examples of using encapsulation
  • Why and when to use it

đŸšȘ What is Encapsulation?

Encapsulation is the practice of bundling data (variables) and methods (functions) that operate on that data into a single unit—typically a class. More importantly, it controls who can access or modify that data.

Imagine you’re using a vending machine. You don’t need to know how it works internally—you just press a button. Similarly, in encapsulated code, users interact through defined interfaces (methods), without worrying about the internal logic.


đŸ§± Benefits of Encapsulation

  • ✅ Protects object integrity by preventing outside interference
  • ✅ Simplifies code and hides complexity
  • ✅ Encourages modular, maintainable design
  • ✅ Makes the code easier to debug and test
  • ✅ Enables future-proofing: internals can change without breaking user code

đŸ€« What is Data Hiding?

Data hiding is a part of encapsulation that prevents direct access to class variables from outside the class. This is where _protected and __private come into play in Python.

Unlike other languages that use keywords like private, protected, or public, Python uses naming conventions:

PrefixAccess LevelDescription
NonePublicAccessible from anywhere
_varProtectedShould not be accessed outside the class or subclass
__varPrivateName mangled to prevent outside access

🔓 Public Variables (Default)

Any variable or method that doesn’t start with _ or __ is public.

class Person:
def __init__(self, name):
self.name = name
p = Person("Alice")
print(p.name) # Output: Alice

Here, name is public and can be accessed or modified directly.


đŸ›Ąïž Protected Variables (_single_underscore)

A single underscore (_) before a variable indicates it’s protected, meaning it should not be accessed outside the class or its subclasses.

It’s a convention, not a rule.

class Person:
def __init__(self, name, age):
self.name = name
self._age = age # Protected
def show_age(self):
print(f"{self.name} is {self._age} years old")
p = Person("Bob", 30)
print(p._age) # Technically allowed, but discouraged

The _age attribute can still be accessed, but you’re saying to other developers: “Don’t touch this unless you know what you’re doing.”


🔒 Private Variables (__double_underscore)

Two leading underscores (__) trigger name mangling, where Python internally changes the variable name to include the class name. This makes it harder (not impossible) to access from outside.

class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private
def get_balance(self):
return self.__balance
account = BankAccount(1000)
print(account.get_balance()) # ✅ Output: 1000
# print(account.__balance) ❌ AttributeError

Trying to access __balance directly will fail, but you can still do this:

print(account._BankAccount__balance) # 😅 Output: 1000 (Not recommended)

That’s why it’s called name mangling—Python rewrites __balance internally as _BankAccount__balance.


đŸ§Ș Example: Full Encapsulation with Getters & Setters

You can create fully encapsulated classes by providing controlled access to attributes through getters and setters.

class Employee:
def __init__(self, name, salary):
self.__name = name
self.__salary = salary
def get_salary(self):
return self.__salary
def set_salary(self, amount):
if amount > 0:
self.__salary = amount
else:
print("Invalid salary!")
emp = Employee("John", 50000)
print(emp.get_salary()) # Output: 50000
emp.set_salary(60000)
print(emp.get_salary()) # Output: 60000
emp.set_salary(-1000) # Output: Invalid salary!

Here, direct access to __salary is blocked. Any changes must go through the set_salary() method, which adds validation.


🧠 Best Practices

  • Use public attributes only when safe to expose.
  • Use protected attributes when you want limited access but still allow subclass interaction.
  • Use private attributes when you want to hide implementation details completely.
  • Encapsulate behavior using getters and setters, especially if logic like validation is required.
  • Don’t overuse private attributes unless necessary—Python favors consenting adults over strict access control.

🛠 Use Case: Banking System with Protected and Private Data

class BankAccount:
def __init__(self, name, balance):
self.name = name
self._account_type = "Savings" # Protected
self.__balance = balance # Private
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
def get_balance(self):
return self.__balance
account = BankAccount("Alice", 1000)
account.deposit(500)
print(account.get_balance()) # Output: 1500
print(account._account_type) # Output: Savings (not recommended)
# print(account.__balance) # AttributeError

This example mimics real-world systems where you don’t want users directly modifying the balance—only through trusted interfaces.


🧯 Encapsulation vs Abstraction

Many beginners confuse these two terms:

TermDefinition
EncapsulationBundling data and methods + access control
AbstractionHiding internal logic and showing only relevant features

They work together to secure and simplify code.


✅ Summary

  • Encapsulation in Python controls access to class data using naming conventions.
  • Use:
    • Public for open access
    • _protected for limited access (by subclass)
    • __private for full restriction (via name mangling)
  • Use getters and setters to access private data safely.
  • Python doesn’t enforce strict access control—you, the developer, are responsible.

🎓 Final Thoughts

Python may not have rigid access modifiers like other languages, but its clear and developer-friendly approach to encapsulation works incredibly well. By following simple naming conventions and using built-in mechanisms like name mangling and method-based access, you can build robust and secure class designs.

Encapsulation isn’t about hiding your code—it’s about protecting your data.