Python Private Variables: Name Mangling Explained
python private variables: Learn how Python private variables work through name mangling, underscore conventions, and when to use them for encapsulation.
Python private variables are a common source of confusion because the language does not enforce access control the way Java or C# do. Instead, Python relies on naming conventions and a mechanism called name mangling to signal that an attribute should not be accessed directly. Understanding how these tools work—and where they fall short—is essential for writing maintainable object-oriented code.
What Python Private Variables Actually Mean
Python does not have a private keyword. Every attribute is accessible from outside the class, and there is no compile-time or runtime check that prevents you from reading or writing it. The notion of privacy is expressed through naming conventions that tell other developers, and sometimes the interpreter itself, that an attribute is intended for internal use only.
The two primary conventions are a single leading underscore (_var) and a double leading underscore (__var). The single underscore is purely a convention; the double underscore triggers name mangling, a transformation that makes the attribute name harder to access accidentally. Neither provides true privacy, but each serves a different purpose.
The Single Underscore Convention
A single leading underscore signals that an attribute or method is internal to the class or module. It is a strong hint to other developers that they should not rely on it, but the interpreter does nothing to enforce this. For example:
class DatabaseConnection: def __init__(self, host, port): self._host = host self._port = port def _validate(self): if not self._host or not self._port: raise ValueError("Invalid connection parameters")
Here, _host and _port are intended to be private. A developer reading the code understands that these attributes are part of the internal state and should not be modified directly. However, you can still access them from outside:
db = DatabaseConnection("localhost", 5432) print(db._host) # Works, but violates the convention
The single underscore is the most common way to mark private variables in Python. It is lightweight, does not affect the attribute name, and is widely understood across the community. Use it when you want to communicate intent without adding any runtime behavior.
Double Underscore and Name Mangling
When you prefix an attribute name with two underscores (but do not also end it with two underscores), Python applies name mangling. The interpreter rewrites the attribute name inside the class definition to include the class name. For example:
class Counter: def __init__(self): self.__count = 0 def increment(self): self.__count += 1 def get_count(self): return self.__count
In the above class, self.__count is transformed at compile time to self._Counter__count. This means the attribute is stored under the mangled name, not the original one. From outside the class, accessing counter.__count raises an AttributeError because that attribute does not exist. However, you can still access the mangled name directly:
counter = Counter() counter.increment() print(counter._Counter__count) # 1
Name mangling is a compile-time transformation. It applies to any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) that appears inside a class definition. It does not apply to identifiers that both start and end with two underscores, such as __init__ or __str__, because those are reserved for Python's special methods.
How Name Mangling Affects Attribute Access
The main effect of name mangling is to make accidental access from outside the class more difficult. It also prevents name collisions in inheritance hierarchies. Consider a base class and a subclass that both define an attribute with the same double-underscore name:
class A: def __init__(self): self.__value = 10 class B(A): def __init__(self): super().__init__() self.__value = 20
In this example, A.__value becomes _A__value, and B.__value becomes _B__value. The two attributes are completely separate, even though they have the same logical name. This is useful when you want to ensure that a subclass does not accidentally overwrite an internal attribute of its parent.
However, name mangling does not protect against deliberate access. A developer who knows the mangling rule can always access the attribute. It also does not apply to string-based access. For instance, getattr(counter, "__count") will raise an AttributeError because the string is not mangled at runtime. The mangling happens only when the interpreter processes the source code, not when you pass a string to a function.
When Name Mangling Does and Doesn't Apply
Name mangling applies only to identifiers that appear directly in a class body. It does not apply to attributes created dynamically after the object exists. For example, assigning obj.__new_attr = 1 outside the class will create an attribute literally named __new_attr, not a mangled name. This can lead to confusing behavior if you mix dynamic assignment with name-mangled attributes.
It also does not apply to names that start with a single underscore or to names that start and end with two underscores. The rule is specifically for names with two or more leading underscores and at most one trailing underscore. This is why special methods like __init__ are not mangled.
Another important limitation is that name mangling is lexical, not dynamic. It applies to the code as written, not to the actual attribute lookup at runtime. If you use setattr or getattr with a string, no mangling occurs. This can be surprising when you are working with dynamic attribute access or serialization libraries.
Practical Encapsulation Patterns
For most use cases, the single underscore convention is sufficient. It communicates intent without adding the complexity of name mangling. Use double underscores only when you have a specific need to prevent name collisions in subclasses or when you want to make accidental external access even less likely.
If you need to control how an attribute is read or written, use a property instead of relying on naming conventions. Properties allow you to add validation, caching, or computed values while keeping the public interface clean. For example:
class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Temperature below absolute zero") self._celsius = value
Here, the attribute _celsius is private by convention, and the property celsius provides controlled access. This is a common pattern that gives you the benefits of encapsulation without relying on name mangling.
Maintainability and Compatibility Considerations
Name mangling has a real impact on maintainability. Because the attribute name changes, debugging tools, profilers, and serialization libraries that rely on attribute names may show the mangled version. This can make logs and stack traces harder to read. It also means that any code that accesses the attribute by name must use the mangled form, which is fragile if the class name changes.
From a compatibility perspective, name mangling is a compile-time feature that is stable across Python versions. It has been part of the language for decades and is unlikely to change. However, relying on it heavily can make your code less transparent to other developers, who may not expect the transformation.
There is no runtime performance cost to name mangling. The transformation happens when the class is defined, and attribute access uses the same underlying mechanism as any other attribute. The only cost is the extra characters in the attribute name, which is negligible.
Choosing the Right Approach
Use the single underscore convention when you want to mark an attribute as internal without any additional behavior. It is the most common and least surprising choice. Use double underscores when you are building a class hierarchy and need to prevent accidental overrides of internal attributes by subclasses. Use properties when you need to validate or transform attribute access.
There is no reason to use name mangling for every private variable. In most codebases, the single underscore is enough. Overusing double underscores can make the code harder to maintain and read. The key is to match the mechanism to the actual requirement: communication, collision avoidance, or controlled access.
When you do use name mangling, be aware of its limitations. It does not provide security, and it does not prevent a determined developer from accessing the attribute. It is a tool for signaling intent and reducing accidental mistakes, not for enforcing access control. Treat it as such, and your code will be clearer and more maintainable.