class User:
def __init__(self, name):
self.name = name # Public — accessible anywhere
u = User('Alice')
print(u.name) # 'Alice'
u.name = 'Bob' # Direct modification — no restrictionsclass BankAccount:
def __init__(self, balance):
self._balance = balance # Protected by convention
def _validate(self, amount): # Protected method
return amount > 0
# _balance is STILL accessible — it's just a CONVENTION
acc = BankAccount(1000)
print(acc._balance) # 1000 (works, but you shouldn't!)
acc._balance = -500 # Works, but violates the conventionclass Secret:
def __init__(self):
self.__hidden = 'secret data'
def get_hidden(self):
return self.__hidden
s = Secret()
# print(s.__hidden) # AttributeError!
print(s.get_hidden()) # 'secret data'
# Name mangling: __hidden becomes _Secret__hidden
print(s._Secret__hidden) # 'secret data' (works but DON'T do this!)class Temperature:
def __init__(self, celsius):
self.__celsius = celsius
def get_celsius(self):
return self.__celsius
def set_celsius(self, value):
if value < -273.15:
raise ValueError('Below absolute zero!')
self.__celsius = value
t = Temperature(25)
t.set_celsius(30)
print(t.get_celsius()) # 30
# t.set_celsius(-300) # ValueError!class Parent:
def __init__(self):
self.__value = 'parent'
class Child(Parent):
def try_access(self):
# self.__value looks for _Child__value, NOT _Parent__value!
# return self.__value # AttributeError!
return self._Parent__value # Works with mangled name
c = Child()
print(c.try_access()) # 'parent'Create a `BankAccount` class with `__balance` (private), `_transaction_log` (protected), and `owner` (public). Add `deposit(amount)` and `withdraw(amount)` methods that validate amount > 0 and update `__balance`. Add `get_balance()` getter. Show that `__balance` can't be accessed directly but `_transaction_log` can (with a warning comment). Demonstrate valid and invalid operations.