Access Modifiers: Public, Protected, and Private
In Object-Oriented Programming, a key principle is encapsulation: the idea of bundling data (attributes) and the methods that operate on that data into a single unit (an object). Part of this principle is controlling access to that data. You might want some attributes to be freely accessible, while others should be hidden or only modified through specific methods.
Languages like Java and C++ use keywords like public, private, and protected to strictly enforce this. Python's approach is different. It relies on naming conventions to signify the intended visibility of an attribute or method. There are no truly "private" members in Python, but these conventions are a strong signal to other developers about how a class should be used.
π Prerequisitesβ
You should be comfortable defining a class with attributes and methods.
π― Article Outline: What You'll Masterβ
In this article, you will learn:
- β Public Members: The default behavior for all attributes and methods.
- β Protected Members (Single Underscore): A convention to indicate an attribute is for internal use.
- β Private Members (Double Underscore): A convention, enforced by "name mangling," to strongly discourage external access.
- β Name Mangling: Understand how Python changes the names of private members.
π§ Section 1: Public Members (No Underscores)β
By default, every attribute and method you've created so far has been public. This means it can be freely accessed and modified from anywhereβinside the class, by subclasses, or from outside the class entirely.
You don't need to do anything special to make a member public; it's the default.
class PublicBankAccount:
def __init__(self, owner, balance):
self.owner = owner # Public attribute
self.balance = balance # Public attribute
def deposit(self, amount): # Public method
self.balance += amount
account = PublicBankAccount("Alice", 1000)
# Public members can be accessed from anywhere
print(f"Owner: {account.owner}")
account.balance = -500 # We can directly modify the balance, which might be dangerous!
print(f"New Balance: {account.balance}")
The ability to set the balance to a negative number from outside the class shows the danger of having everything public. We have no control over the data's integrity.
π» Section 2: Protected Members (Single Underscore)β
To signal that an attribute or method is intended for internal use and should not be accessed directly from outside the class, you prefix its name with a single underscore (_).
This is purely a convention. Python does not stop you from accessing a protected member. It's a "gentleman's agreement" among developers that says, "You can touch this, but you probably shouldn't." It's often used for internal helper methods or attributes that subclasses might need to access.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
# This attribute is now marked as 'protected'
self._balance = balance
def deposit(self, amount):
if amount > 0:
self._balance += amount
self._log_transaction(f"Deposited ${amount}")
# This is an internal helper method, marked as 'protected'
def _log_transaction(self, message):
print(f"[LOG] {message}")
account = BankAccount("Bob", 2000)
account.deposit(500)
# You CAN still access it, but the underscore signals that you shouldn't.
# This is considered bad practice.
# account._balance = -9999
# print(account._balance)
π οΈ Section 3: Private Members (Double Underscore)β
To strongly discourage access to an attribute or method, you can prefix its name with a double underscore (__). This is more than just a convention; it triggers a mechanism called name mangling.
Name Manglingβ
When the Python interpreter sees an attribute name starting with __ (but not ending with it), it secretly changes the name to _ClassName__attribute_name.
This makes it impossible to access the attribute directly using its original name from outside the class, effectively making it private.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
# This attribute is now 'private'
self.__balance = balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
self.__log_transaction(f"Deposited ${amount}")
else:
print("Deposit amount must be positive.")
def get_balance(self):
"""A public method to safely access the balance."""
return self.__balance
# This is a private method
def __log_transaction(self, message):
print(f"[PRIVATE LOG] {message}")
account = BankAccount("Charlie", 5000)
account.deposit(1000)
# This public method is the correct way to see the balance
print(f"Current balance: {account.get_balance()}")
# This will now cause an AttributeError!
try:
print(account.__balance)
except AttributeError as e:
print(f"\nError accessing private attribute: {e}")
# You can still access it if you know the mangled name (but don't do this!)
print(f"Mangled name access: {account._BankAccount__balance}")
Output:
[PRIVATE LOG] Deposited $1000
Current balance: 6000
Error accessing private attribute: 'BankAccount' object has no attribute '__balance'
Mangled name access: 6000
By making __balance private, we force the user of the class to use our public methods (deposit, get_balance), where we can control the logic and ensure the balance never becomes invalid. This is a core part of encapsulation.
β¨ Conclusion & Key Takeawaysβ
Python's approach to access control is based on convention and trust rather than strict enforcement. Using these naming patterns correctly is key to communicating your intent as a class designer.
Let's summarize the key takeaways:
- Public (e.g.,
name): No underscores. Accessible from anywhere. This is the default. - Protected (e.g.,
_name): A single underscore prefix. A convention indicating "for internal use only," but not enforced. Subclasses are generally expected to be able to use these. - Private (e.g.,
__name): A double underscore prefix. Enforced by name mangling (_ClassName__name). Strongly discourages access from outside the class.
Challenge Yourself:
Create a Temperature class that stores a temperature in Celsius.
- Store the Celsius value in a private attribute (e.g.,
__celsius). - Create a public method
get_celsius()to return the temperature. - Create a public method
set_celsius(value)that checks if the given value is above absolute zero (-273.15Β°C) before setting the private attribute. If it's not, print an error message.
β‘οΈ Next Stepsβ
We've seen how to control access to data within a single class. The next major OOP concept is "Encapsulation: Bundling data and methods that operate on the data," where we'll formalize the ideas we've touched on here and see how it leads to more robust and maintainable code.
Happy coding!