Master Guide to Encapsulation and Name Mangling in Python

Hello Everyone! Today we are discussing Encapsulation, another core pillar of Object-Oriented Programming (OOP).

Encapsulation involves bundling data (attributes) and the methods (functions) that operate on that data into a single unit: the class. It is the practice of hiding an object’s internal complexity from the outside world and exposing only a clean, controlled interface to interact with it.

Think of a TV remote control: you interact with the buttons (the public interface), but you don’t need to see or understand the internal circuit board (the private data and implementation).

1. Core Concepts: Protected Convention (_var)

In languages like Java or C++, you have strict keywords like private and protected. Python does not have built-in access modifiers. Instead, it relies on naming conventions to signal privacy boundaries.

Using a single underscore (_) before an attribute name tells other developers: “This is a protected property intended for internal use only. Please do not modify it directly outside this class.”

class BankAccount:
    def __init__(self, initial_balance):
        self._balance = initial_balance  # Protected attribute by convention

    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
            print(f"Deposited {amount}. New Balance: {self._balance}")

    def withdraw(self, amount):
        if 0 < amount <= self._balance:
            self._balance -= amount
            print(f"Withdrew {amount}. New Balance: {self._balance}")

class User(BankAccount):
    def get_balance(self):
        # Subclasses can access protected variables cleanly
        print(f"User check balance: {self._balance}")

account = User(500)
account.get_balance()  # Output: User check balance: 500
print(account._balance) # Valid, but considered poor practice because it skips the interface!


2. Hardening Class Space: Name Mangling (__var)

When you prefix a class attribute with a double underscore (__), Python enforces a strict rule called Name Mangling.

Python’s interpreter automatically rewrites the attribute name behind the scenes, transforming __variable into _ClassName__variable. This is designed to prevent name clashes in subclasses, making it harder for child classes or external code to accidentally overwrite important internal values.

class Car:
    def __init__(self, brand, mileage):
        self.__brand = brand      # Scrambled to: _Car__brand
        self.__mileage = mileage  # Scrambled to: _Car__mileage

    def get_info(self):
        print(f"This is a {self.__brand} with {self.__mileage} miles.")

class ElectricCar(Car):
    def __init__(self, brand, mileage, battery_level):
        super().__init__(brand, mileage)
        self.__battery_level = battery_level # Scrambled to: _ElectricCar__battery_level

    def get_info(self):
        # Accessing inherited mangled properties requires calling the scrambled parent name
        print(f"Brand: {self._Car__brand}, Battery: {self.__battery_level}%")

# Execution Test
my_ev = ElectricCar("Tesla", 10000, 95)
my_ev.get_info() # Output: Brand: Tesla, Battery: 95%

What Happens If You Inspect the Object?

If you pass the object to Python’s built-in directory discovery function dir(), you can clearly see the name mangling translation mapping at work:

print(dir(my_ev))
# Output includes: '_Car__brand', '_Car__mileage', '_ElectricCar__battery_level'

Because of this rewriting behavior, trying to run print(my_ev.__mileage) from outside the class will throw an AttributeError crash. However, you can technically still bypass it by writing print(my_ev._Car__mileage). This shows that name mangling is a tool for namespace protection rather than true data security.


3. Summary: Single vs. Double Underscore Comparison

Syntax Structure Name Classification Behavior & Intent
self.value Public Accessible from anywhere. Represents the open interface of your object.
self._value Protected Convention Only. Signals internal intent. Python does not alter or block access to this attribute. This is the preferred way to label private data in Python.
self.__value Private / Mangled Enforced Rule. Python automatically rewrites the variable name to _ClassName__value to protect namespaces from conflicts during inheritance.