Public, Protected, Private Mem
Part of the Object Oriented Programming section of Coddy's Python journey — lesson 28 of 64.
Python has three levels of access control for class members: public, protected, and private. These control how attributes and methods can be accessed.
Here is an example of a class with all three access levels:
class BankAccount:
def __init__(self, owner, balance, account_id):
self.owner = owner # Public - accessible anywhere
self._balance = balance # Protected - internal use
self.__account_id = account_id # Private - class only
def deposit(self, amount): # Public method
self._balance += amount
def _calculate_interest(self): # Protected method
return self._balance * 0.02
def __validate_transaction(self, amount): # Private method
return amount > 0 and amount <= self._balanceAccess public members from anywhere:
account = BankAccount("Alice", 1000, "12345")
print(account.owner) # Alice
account.deposit(500) # Works fineAccess protected members (single underscore - convention only):
print(account._balance) # 1500 - works but not recommended
result = account._calculate_interest() # Works but not recommended
print(result) # 30.0Try to access private members (double underscore - name mangled):
# This won't work:
# print(account.__account_id) # AttributeError
# But this works (name mangling):
print(account._BankAccount__account_id) # 12345Create a subclass to show protected vs private access:
class SavingsAccount(BankAccount):
def show_balance(self):
return self._balance # Protected - accessible in subclass
def show_id(self):
# return self.__account_id # This won't work - private
return "Cannot access private member"savings = SavingsAccount("Bob", 2000, "67890")
print(savings.show_balance()) # 2000
print(savings.show_id()) # Cannot access private memberOutput:
Alice
30.0
12345
2000
Cannot access private memberKey Point: Public members have no prefix and are accessible anywhere. Protected members use single underscore (_) and should only be used within the class hierarchy. Private members use double underscore (__) and are name-mangled for stronger privacy. Python's access control is convention-based, not strictly enforced.
Challenge
MediumIn this challenge, you'll implement a BankAccount class that demonstrates proper use of public, protected, and private access levels in Python.
bankaccount.py- Contains the class definition with TODO comments guiding your implementation
- Follow the TODO comments in
bankaccount.pyto implement the required functionality - Implement proper access levels (public, protected, private) as specified
- Ensure all methods handle edge cases appropriately (negative deposits, etc.)
Cheat sheet
Python has three access control levels for class members:
- Public: No prefix, accessible anywhere
- Protected: Single underscore (
_), internal use within class hierarchy - Private: Double underscore (
__), name-mangled for class-only access
class BankAccount:
def __init__(self, owner, balance, account_id):
self.owner = owner # Public
self._balance = balance # Protected
self.__account_id = account_id # Private
def deposit(self, amount): # Public method
self._balance += amount
def _calculate_interest(self): # Protected method
return self._balance * 0.02
def __validate_transaction(self, amount): # Private method
return amount > 0 and amount <= self._balanceAccessing members:
account = BankAccount("Alice", 1000, "12345")
# Public access
print(account.owner) # Works fine
# Protected access (works but not recommended)
print(account._balance) # 1500
# Private access (name mangled)
print(account._BankAccount__account_id) # 12345In subclasses:
class SavingsAccount(BankAccount):
def show_balance(self):
return self._balance # Protected - accessible
def show_id(self):
# return self.__account_id # Private - not accessible
return "Cannot access private member"Try it yourself
from bank_account import BankAccount
# Comprehensive test case handler
test_case = input()
if test_case == "basic_test":
# Basic functionality test
account = BankAccount("John Doe", "12345")
print(account.account_holder) # Public attribute
print(account.deposit(100)) # Public method
print(account.get_balance()) # Public method
elif test_case == "transaction_count":
# Testing protected attribute
account = BankAccount("Jane Smith", "67890")
account.deposit(50)
account.deposit(100)
account.deposit(200)
print(account._transaction_count) # Protected attribute
print(account.get_balance())
elif test_case == "invalid_deposit":
# Testing validation in deposit method
account = BankAccount("Bob Johnson", "54321")
print(account.deposit(-50)) # Should return None
print(account.deposit(0)) # Should return None
print(account.deposit(75)) # Should return 75
print(account.get_balance())
elif test_case == "private_access":
# Testing private attribute access
account = BankAccount("Alice Brown", "98765")
try:
print(account.__balance) # This will raise an AttributeError
except AttributeError:
pass
try:
print(account.__account_number) # This will raise an AttributeError
except AttributeError:
pass
print("Private attributes are not directly accessible")
account.deposit(500)
print(account.get_balance()) # Access through public method
elif test_case == "name_mangling":
# Demonstrating name mangling for private attributes
account = BankAccount("Charlie Green", "13579")
print(account._BankAccount__balance) # Accessing private attribute through name mangling
print(account._BankAccount__account_number) # Accessing private attribute through name mangling
account.deposit(300)
print(account._BankAccount__balance) # Should show updated balance
elif test_case == "multiple_accounts":
# Testing multiple accounts
account1 = BankAccount("David White", "24680")
account2 = BankAccount("Eva Black", "86420")
account3 = BankAccount("Frank Red", "97531")
account1.deposit(150)
account2.deposit(250)
account2.deposit(50)
account3.deposit(500)
account3.deposit(300)
account3.deposit(200)
print(f"{account1.account_holder}: {account1.get_balance()}")
print(f"{account2.account_holder}: {account2.get_balance()}")
print(f"{account3.account_holder}: {account3.get_balance()}")
print(f"Account 1 transactions: {account1._transaction_count}")
print(f"Account 2 transactions: {account2._transaction_count}")
print(f"Account 3 transactions: {account3._transaction_count}")
elif test_case == "stress_test":
# Performance test with many transactions
account = BankAccount("Stress Tester", "11111")
for _ in range(1000):
account.deposit(1)
print(f"Final balance: {account.get_balance()}")
print(f"Transaction count: {account._transaction_count}")
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe self ParameterMethodsAttributesConstructor Method (__init__)Recap - Simple Calculator4Inheritance
Basic InheritanceThe super() FunctionMethod OverridingMultiple InheritanceMethod Resolution OrderRecap - Employee Hierarchy7Special Methods
Magic Methods IntroductionOperator OverloadingContainer Magic MethodsRecap - Custom List10Design Patterns Part 1
Intro to design patternSingleton PatternFactory PatternObserver PatternStrategy Pattern2Decorators
Introduction to DecoratorsProperty DecoratorStatic Method DecoratorClass Method Decorator5Polymorphism
Method Overriding RevisitedDuck TypingAbstract Classes and MethodsInterface DesignRecap - Shape Calculator8Advanced OOP Concepts
Composition vs InheritanceMixinsStatic and Class MethodsClass DecoratorsContext Managers3Class Properties
Instance vs Class VariablesProperty DecoratorsPrivate AttributesRecap - Bank Account Manager6Encapsulation
Public, Protected, Private MemAccess ModifiersInformation HidingProperty Decorators AdvancedRecap - Student Records System12Project: Library Management
Project OverviewBook and User Classes