Back to Blog
Python

Python Private Attributes: Syntax and Limits

python private attribute: Understand how Python private attributes work through name mangling, why they aren't truly private, and when to use them for maintainable code.

name manglingencapsulationobject-oriented programmingdunder attributesaccess control
Illustration of a Python class with a locked private attribute and a visible public interface.

When a developer searches for python private attribute, they usually want to know how to prevent external code from modifying an object's internal state. Python does not have true private attributes in the way Java or C# do. Instead, it provides a convention and a syntactic mechanism that discourages accidental access. The double underscore prefix triggers name mangling, which rewrites the attribute name at compile time. This article explains exactly how that works, where it helps, and where it can mislead you.

The Double Underscore Rule

Writing self.__balance inside a class does not create a private attribute in the strict sense. It triggers name mangling: the interpreter rewrites the attribute name to _ClassName__balance. This happens regardless of where the attribute is defined, as long as it appears inside a class body.

class BankAccount: def __init__(self, initial_balance): self.__balance = initial_balance def get_balance(self): return self.__balance

After the class is defined, the attribute is not stored as __balance. It is stored as _BankAccount__balance. You can verify this by inspecting the instance dictionary.

account = BankAccount(100) print(account.__dict__) # {'_BankAccount__balance': 100}

The mangled name is the reason external code that tries to access account.__balance raises an AttributeError. The attribute simply does not exist under that name. This prevents accidental access, but it does not prevent deliberate access, because the mangled name is predictable.

Why Name Mangling Exists

Name mangling is designed to avoid accidental name collisions in inheritance hierarchies, not to enforce security. Consider a base class and a subclass that both define an attribute with the same name.

class Base: def __init__(self): self.__value = 1 def get_value(self): return self.__value class Sub(Base): def __init__(self): super().__init__() self.__value = 2

Without mangling, the subclass assignment would silently overwrite the base class attribute. With mangling, the base class stores _Base__value and the subclass stores _Sub__value. Each class continues to access its own attribute through the original name inside its own methods. This is the primary practical benefit of the double underscore prefix.

How to Access a Private Attribute

Because name mangling is deterministic, any caller can still reach the attribute by using the mangled name directly.

account = BankAccount(100) print(account._BankAccount__balance) # 100

This works, but it is a deliberate violation of the class's intended interface. The mangled name is an implementation detail, not a public API. Relying on it makes your code fragile because the class author can change the internal attribute name without considering it a breaking change.

There is also a subtlety with dynamic attribute access. The getattr function does not perform name mangling automatically.

getattr(account, "__balance") # AttributeError getattr(account, "_BankAccount__balance") # 100

If you are writing generic code that inspects objects, you must know the exact mangled name. This is another reason to treat private attributes as an interface boundary rather than a security mechanism.

Single Underscore: A Convention Only

A single leading underscore, such as self._balance, is purely a convention. It signals to other developers that the attribute is intended for internal use, but the interpreter does nothing to enforce it.

class Account: def __init__(self): self._balance = 0 account = Account() account._balance = 500 # Works fine

Many Python libraries use single underscores for internal state because it keeps the attribute accessible for debugging, testing, and subclassing without pretending to hide it. The double underscore adds name mangling, which can complicate subclassing and debugging. If you do not need collision protection, a single underscore is often the better choice.

Properties: The Pythonic Way to Control Access

When you want to control how an attribute is read or written, the @property decorator is the standard tool. It allows you to expose an attribute-like interface while keeping the actual storage private.

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 _celsius is the internal storage, and celsius is the public property. Callers use temp.celsius for both reading and writing. The setter runs validation logic, and the attribute cannot be set to an invalid value through the normal interface. This is the recommended pattern when you need to enforce invariants.

Properties do not prevent direct writes to _celsius. A caller can still do temp._celsius = -500. But the public interface is clean, and the single underscore makes the intent clear.

Common Mistakes and Misunderstandings

One common mistake is using double underscores for every attribute out of a desire for "privacy." This creates unnecessary friction. Subclasses cannot easily override or extend the attribute, and debugging becomes harder because the attribute name is transformed. Another mistake is assuming that name mangling protects against malicious code. It does not. Any determined caller can access the mangled name.

A more subtle issue arises with methods. Name mangling applies to method names too.

class Service: def __run(self): print("running") service = Service() service.__run() # AttributeError service._Service__run() # running

The same collision-avoidance benefit applies to methods, but so does the same predictability. If you intend a method to be overridden in a subclass, a double underscore prefix will make that harder because the subclass would need to use the mangled name.

Another frequent error is using __ in code that is executed outside a class body. Name mangling only happens inside a class definition. At module level, __value is just a normal name.

__value = 10 # No mangling at module level

This is not an error, but it is confusing because the name looks like a private attribute when it is actually a module-level variable.

Maintainability and Compatibility Considerations

Name mangling is a compile-time transformation, so it has no runtime performance cost. The attribute lookup is the same as for any other attribute. The real cost is in maintainability. When you rename a class, the mangled attribute names change. If any code relied on the mangled name, it breaks silently.

Consider a refactoring that renames BankAccount to Account. The attribute _BankAccount__balance becomes _Account__balance. Any external code that accessed _BankAccount__balance now fails. This is a strong argument for never using mangled names outside the class that defines them.

For public APIs, the double underscore prefix is rarely appropriate. It makes subclassing and extension unnecessarily difficult. Libraries that need to expose internal state for testing or debugging typically use a single underscore. The double underscore is best reserved for attributes that are truly internal to a class and likely to collide in a deep inheritance hierarchy.

When you design a class, decide whether you need collision protection or interface enforcement. If you need collision protection, use __. If you need to prevent invalid values, use a property with a setter. If you simply want to signal intent, use _. Each mechanism serves a different purpose, and mixing them without reason creates code that is harder to maintain.

python private attribute: Practical Usage and Code Examples | RYUSLOG DEV