Python's philosophy on data privacy is distinct from languages like Java or C++. Instead of strict private or protected keywords, Python relies on naming conventions and encapsulation mechanisms to signal intent and control access. The mantra is "We are all consenting adults here," meaning privacy is advisory rather than mandatory.
This guide explains how to use naming conventions, property decorators, and __slots__ to enforce attribute privacy effectively.
Understanding Python's Privacy Model
In Python, there is no way to make an attribute strictly inaccessible from outside the class. However, you can make it harder to access accidentally.
- Public (
name): Accessible everywhere. - Protected (
_name): Convention only. Signals "internal use." - Private (
__name): Name mangling applied. Harder to access accidentally.
Method 1: Protected Attributes (Single Underscore)
Prefixing an attribute with a single underscore _ signals to other developers (and IDEs/linters) that it is intended for internal use only. It does not prevent access technically.
class DatabaseConnection:
def __init__(self):
self.host = "localhost" # Public
self._password = "secret123" # Protected (Convention)
db = DatabaseConnection()
print(db.host)
print(db._password)Output:
localhost
secret123Method 2: Private Attributes (Double Underscore / Mangling)
Prefixing an attribute with two underscores __ (and no trailing underscores) triggers Name Mangling. Python internally renames the attribute to _ClassName__attributeName. This prevents accidental collisions in subclasses and makes direct access harder.
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private
def get_balance(self):
return self.__balance
account = BankAccount(1000)
print(f"Balance: {account.get_balance()}")
try:
print(account.__balance)
except AttributeError as e:
print(f"Error: {e}")
print(f"Mangled Access: {account._BankAccount__balance}")Output:
Balance: 1000
Error: 'BankAccount' object has no attribute '__balance'
Mangled Access: 1000Method 3: Using Property Decorators (Encapsulation)
The most robust way to "enforce" privacy logic (like read-only access or validation) is using properties. You store the data in a protected/private attribute but expose it via @property.
class User:
def __init__(self, username):
self._username = username # Internal storage
@property
def username(self):
return self._username
@username.setter
def username(self, new_name):
if not new_name:
raise ValueError("Username cannot be empty")
self._username = new_name
user = User("Alice")
print(user.username)
try:
user.username = ""
except ValueError as e:
print(f"Error: {e}")Output:
Alice
Error: Username cannot be emptyConclusion
To enforce attribute privacy in Python:
- Use
_attributefor internal implementation details (Standard Convention). - Use
__attributeonly to prevent name collisions in inheritance scenarios (Name Mangling). - Use
@propertyto control access logic (Read-only, Validation) while keeping a clean public API.