Implementing Encapsulation via Name Mangling in Python
Mechanism of Name Mangling
When an identifier is prefixed with double underscores (e.g., __variable) within a class definition, Python interprets this as a request to transform the name to prevent accidental collisions in subclasses. The interpreter rewrites the name to include the class name as a prefix, effectively turning __attribute into _ClassName__attribute.
Inheritance and Access Control
The following example illustrates how name mangling preserves separate namespaces in an inheritance hierarchy. The BaseComponent and DerivedSystem both define a variable named __internal_config. Without mangling, the subclass would overwrite the parent's attribute.
class BaseComponent:
def __init__(self):
self.shared_data = "Global State"
self.__internal_config = "Base Configuration"
def __secure_action(self):
print("Executing base secure operation")
class DerivedSystem(BaseComponent):
def __init__(self):
super().__init__()
# This attribute does not overwrite the parent's __internal_config
self.__internal_config = "Derived Configuration"
def access_layers(self):
print(f"Shared: {self.shared_data}")
# Accessing the subclass's own mangled attribute
print(f"Derived Internal: {self.__internal_config}")
# Explicitly accessing the parent's mangled attribute
print(f"Base Internal: {self._BaseComponent__internal_config}")
self._BaseComponent__secure_action()
system = DerivedSystem()
system.access_layers()
Executing this script demonstrates that the parent's private data remains intact and accessible only via the mangled name:
Shared: Global State
Derived Internal: Derived Configuration
Base Internal: Base Configuration
Executing base secure operation
Simulating Private Members
While Python does not enforce strict access control like C++ or Java, name mangling serves as a strong signal that a member is internal and should not be touched from outside the class. Attempting to access a double-underscore attribute by its original name results in an AttributeError.
class DataProcessor:
def __init__(self):
self.__secret_key = 998877
def __encrypt(self):
print("Processing data...")
processor = DataProcessor()
# The following lines will raise AttributeError
# processor.__secret_key
# processor.__encrypt()
# Correct access via name mangling
print(processor._DataProcessor__secret_key)
Best Practices for Underscore Usage
Developers must choose between single and double underscores based on the specific design intent:
- Single Underscore (
_var): Use this by default for internal attributes or methods. It indicates a protected member by convention, signaling to other programmers that it should not be accessed outside the API, though it remains accessible. - Double Underscore (
__var): Use this only when you need to ensure that a variable is localized to the specific class and cannot be accidentally shadowed by a subclass using the same name. This adds complexity and should be used sparingly. - Trailing Underscore (
var_): Use this to avoid naming conflicts with Python keywords. For example, to define a variable namedtype, usetype_instead.