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 discouragedOutput:
This is a protected variable🔹 Key Points:
_ais 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-manglingOutput:
Hidden value🔍 What is Name Mangling?
Python automatically changes the name of any identifier starting with __ and not ending with __ to:
_ClassName__variableNameSo 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:
| Method | Description |
|---|---|
__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
| Pattern | Example | Name | Purpose | Access Control |
|---|---|---|---|---|
_a | _var | Single leading underscore | Internal use (“protected”) | Accessible (by convention only) |
__a | __var | Double leading underscore | Name-mangling (“private”) | Harder to access directly |
__a__ | __init__, __len__ | Double underscore on both sides | Special or magic methods | Reserved 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:
| Notation | Meaning | Accessibility | Example Use |
|---|---|---|---|
_var | Protected / internal use (by convention) | Accessible | _temp, _cache |
__var | Private (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,publicin Java or C++).
Instead, it uses underscore conventions to signal intended usage and scope.
