আগের OOP lesson এর পেছনের mechanism ব্যাখ্যা না করেই @property ব্যবহার করেছিল। একটি decorator এমন একটি function যা অন্য একটি function নেয়, এটাকে অতিরিক্ত behavior দিয়ে wrap করে, আর wrap করা version return করে — @ সিনট্যাক্স একটা প্রয়োগ করার একটা সুবিধাজনক উপায় মাত্র।
def shout(func):
def wrapper():
result = func()
return result.upper()
return wrapper
def greet():
return 'hello'
loud_greet = shout(greet)
print(loud_greet()) # HELLOdef shout(func):
def wrapper():
result = func()
return result.upper()
return wrapper
@shout
def greet():
return 'hello'
print(greet()) # HELLO — @shout স্বয়ংক্রিয়ভাবে apply হয়def greet():-এর উপরে @shout ঠিক define করার পরে greet = shout(greet) লেখার সমতুল্য।
একটা আসল decorator-এর wrap করা function আগে থেকে না জেনেই যা যা argument নেয় তা pass through করা দরকার।
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f'{func.__name__} took {time.time() - start:.4f}s')
return result
return wrapper
@timer
def slow_add(a, b):
time.sleep(1)
return a + b
slow_add(2, 3) # print করে: slow_add took 1.0003s| Decorator | কী করে |
|---|---|
| @property | একটা method-কে attribute-এর মতো access করতে দেয় — আগের OOP lesson-এ কভার করা |
| @staticmethod | একদম self ব্যবহার করে না এমন একটা method চিহ্নিত করে |
| @classmethod | একটা instance-এর বদলে ক্লাস নিজেই (cls) পাঠায় |
| @functools.lru_cache | একই argument দিয়ে বারবার call-এর জন্য একটা function-এর return মান cache করে |
কেন এই pattern আসল কোডে সবখানে আছে
Logging, timing, caching, access check — এগুলো প্রয়োগ হওয়া প্রতিটা function-এর ভেতরে সেই logic না repeat করে cross-cutting behavior যোগ করার সবচেয়ে পরিষ্কার উপায়গুলোর একটা decorator।