In Python, prefixing and suffixing variable names with underscores has special meanings.
They are not just stylistic — they indicate different levels of visibility, name mangling, or reserved usage.

Let’s break them down clearly 👇


🧩 **1. _a → (Single Underscore Prefix) → “Protected” or “Internal Use” Variable

A single underscore (_) before a variable name is a convention (not enforced by Python).
It indicates that the variable is intended for internal use only, and should not be accessed directly from outside the class or module.

Example:

class MyClass:
    def __init__(self):
        self._data = "This is a protected variable"

obj = MyClass()
print(obj._data)  # ⚠️ Technically allowed, but discouraged

Output:

This is a protected variable

🔹 Key Points:

  • _a is a convention for internal use (not truly private).
  • You can still access _a — Python does not restrict it.
  • Commonly used to signal “don’t use this outside the class/module”.

🧠 Used In Imports:

When you import using from module import *,
Python ignores all names starting with _.

# mymodule.py
_a = 10   # internal
b = 20

# main.py
from mymodule import *
print(b)   # ✅ Works
print(_a)  # ❌ NameError (ignored during import)

Output:

20
NameError: name '_a' is not defined

✅ So, single underscores protect variables from being imported with *.


🧩 **2. __a → (Double Underscore Prefix) → “Private” Variable (Name Mangling)

A variable with a double underscore prefix (like __a) is name-mangled by Python to avoid accidental access or override in subclasses.

It becomes harder to access directly from outside the class.

Example:

class MyClass:
    def __init__(self):
        self.__secret = "Hidden value"

obj = MyClass()

# print(obj.__secret)   # ❌ AttributeError
print(obj._MyClass__secret)  # ✅ Access via name-mangling

Output:

Hidden value

🔍 What is Name Mangling?

Python automatically changes the name of any identifier starting with __ and not ending with __ to:

_ClassName__variableName

So in the example above:

__secret → _MyClass__secret

🧠 Purpose:

  • To prevent accidental overrides in subclasses.
  • To hide internal implementation details.
  • Not truly private, but name-mangled to discourage direct access.

Example: Preventing Accidental Override

class Base:
    def __init__(self):
        self.__data = 100

class Derived(Base):
    def __init__(self):
        super().__init__()
        self.__data = 200  # Different variable due to name mangling

d = Derived()
print(d._Base__data)     # 100 (from Base)
print(d._Derived__data)  # 200 (from Derived)

Output:

100
200

✅ Both __data attributes coexist — name mangling prevents collision between Base and Derived.


🧩 **3. __a__ → (Double Underscore Prefix & Suffix) → “Magic” or “Dunder” Names

Names with double underscores before and after (like __init__, __str__, __len__) are special built-in identifiers in Python.

They are also known as dunder methods (Double UNDERscore).

Example:

class MyClass:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f"MyClass Object: {self.name}"

obj = MyClass("Dheeraj")
print(obj)     # Automatically calls __str__()

Output:

MyClass Object: Dheeraj

🧠 Common “Dunder” Methods:

MethodDescription
__init__Object initializer (constructor)
__str__String representation (used by print())
__repr__Developer-friendly object representation
__len__Used by len()
__add__Used by + operator
__getitem__Accessing elements like obj[key]
__enter__, __exit__Used in context managers (with statements)

✅ You should not create your own arbitrary names like __myVar__.
Only use this pattern if you are overriding Python’s special methods.


🧾 Summary Table

PatternExampleNamePurposeAccess Control
_a_varSingle leading underscoreInternal use (“protected”)Accessible (by convention only)
__a__varDouble leading underscoreName-mangling (“private”)Harder to access directly
__a____init__, __len__Double underscore on both sidesSpecial or magic methodsReserved by Python

🧩 Example Showing All Three Together

class Example:
    def __init__(self):
        self.public = "Public"
        self._protected = "Protected"
        self.__private = "Private"

    def __str__(self):
        return f"{self.public}, {self._protected}, {self.__private}"

obj = Example()

print(obj.public)         # ✅ Public
print(obj._protected)     # ⚠️ Accessible but internal
# print(obj.__private)    # ❌ AttributeError
print(obj._Example__private)  # ✅ Access via name mangling
print(obj)                # ✅ Calls __str__()

Output:

Public
Protected
Private
Public, Protected, Private

In short:

NotationMeaningAccessibilityExample Use
_varProtected / internal use (by convention)Accessible_temp, _cache
__varPrivate (name-mangled)Not directly accessible__password, __config
__var__Special / Magic method (reserved by Python)Accessible, special behavior__init__, __str__, __len__

🧠 Quick Summary:

  • _a → “I’m internal, but you can access me if you must.”
  • __a → “I’m private. Don’t touch me directly.”
  • __a__ → “I’m special. Python uses me for built-in behavior.”

In short:

Python doesn’t have true access modifiers (like private, protected, public in Java or C++).
Instead, it uses underscore conventions to signal intended usage and scope.


Scroll to Top