π Quick Recap (Day 13)
You implemented magic methods like
__str__,__add__,__sub__, and__eq__.You built intuitive classes that behave like built-in types.
π― What Youβll Learn Today
What encapsulation means and how to make attributes private.
How to use getter and setter methods to control access.
What decorators are and how they modify functions or methods.
How to write and apply a custom decorator.
π Overview: Encapsulation
Encapsulation hides the internal state of an object and only exposes whatβs necessary. In Python, you can prefix attribute names with underscores to indicate theyβre private:
class Person:
def __init__(self, name, age):
self._name = name # βprotectedβ convention
self.__age = age # βprivateβ name manglingSingle underscore
_namesuggests βinternal use.βDouble underscore
__ageinvokes name mangling to prevent accidental access.
Getters and Setters
Use property decorator to create controlled access:
class Person:
def __init__(self, name, age):
self._name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("Age cannot be negative")
self.__age = value@propertyturnsage()into a getter.@age.setterdefines the setter forage.
π Overview: Decorators
A decorator is a function that takes another function and extends its behavior without modifying its code. Decorators use the @decorator_name syntax.
Example: A simple timing decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"Executed {func.__name__} in {end-start:.4f} seconds")
return result
return wrapper
@timer
def compute_power(x, y):
return x ** y
# Using the decorator
compute_power(2, 10)This prints the execution time each time compute_power is called.
π§ββοΈ Take the Wand and Try Yourself
Create a file named
encap_deco.py.Define a
BankAccountclass:Private attribute
__balanceinitialized to 0.Property
balanceto get the balance.Setter
balanceto allow deposits (value > 0) and raise error otherwise.
Write a decorator
log_actionthat prints the method name and timestamp before calling any method.Apply
@log_actiontodeposit(amount)andwithdraw(amount)methods inBankAccount.
Solution Example (encap_deco.py):
# encap_deco.py
import time
# Decorator
def log_action(func):
def wrapper(self, *args, **kwargs):
print(f"{time.strftime('%H:%M:%S')} - Calling {func.__name__}")
return func(self, *args, **kwargs)
return wrapper
class BankAccount:
def __init__(self):
self.__balance = 0
@property
def balance(self):
return self.__balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("Cannot set negative balance")
self.__balance = value
@log_action
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self.__balance += amount
@log_action
def withdraw(self, amount):
if amount <= 0 or amount > self.__balance:
raise ValueError("Invalid withdrawal amount")
self.__balance -= amount
# Practice
account = BankAccount()
account.deposit(100)
print("Balance:", account.balance)
account.withdraw(30)
print("Balance:", account.balance)Expected output:
HH:MM:SS - Calling deposit
Balance: 100
HH:MM:SS - Calling withdraw
Balance: 70Run:
python encap_deco.pyOnce you see the correct logged actions and balances, youβve mastered encapsulation and decorators!
Up next: Day 15: File I/O & Exception Handling