Python Decorators
Decorator क्या है?
Decorator एक ऐसा function है जो किसी दूसरे function को लेकर उसमें extra काम जोड़कर नया function लौटाता है – मूल code बदले बिना। आप इसे एक बार लिखते हैं और @ से शुरू होने वाली एक line से जोड़ देते हैं।
असली projects में decorators हर जगह हैं: Flask URL को function से जोड़ने के लिए @app.route इस्तेमाल करता है, Django pages बचाने के लिए @login_required, और Python ख़ुद @property, @staticmethod और @functools.lru_cache देता है।
| Decorator के बिना | Decorator के साथ |
|---|---|
| वही logging/timing code 20 functions में copy करना | Logic एक बार लिखें, हर function के ऊपर @log_call लगाएँ |
| नियम बदला → 20 जगह बदलाव | नियम बदला → एक decorator बदलें |
| Business logic और checks मिले हुए | Function साफ़ रहता है; checks बाहर रहते हैं |
Step 1: functions भी objects हैं
Decorators इसलिए काम करते हैं क्योंकि Python में function एक साधारण object है। इसे variable में रख सकते हैं, दूसरे function को दे सकते हैं और function से लौटा सकते हैं।
def greet(name):
return f"Namaste, {name}!"
say = greet # no brackets: we store the function itself
print(say("Riya"))
print(type(greet))
def run_twice(func, value): # a function that receives a function
return func(value) + " " + func(value)
print(run_twice(greet, "Aman"))greet function object है; greet() उसे चलाकर result देता है। Decorator को greet मिलता है, greet() नहीं।
Step 2: function के अंदर function (closure)
Inner function, outer function के ख़त्म होने के बाद भी उसके variables इस्तेमाल कर सकता है। इसी “याद रखने” को closure कहते हैं, और हर decorator इसी पर बना है।
def make_gst_calculator(rate):
def add_gst(amount):
return round(amount * (1 + rate / 100), 2)
return add_gst # return the inner function
gst_5 = make_gst_calculator(5) # sweets
gst_18 = make_gst_calculator(18) # electronics
print(gst_5(200))
print(gst_18(200))Step 3: आपका पहला decorator
अब दोनों विचार जोड़ें। log_call एक function लेता है, एक wrapper बनाता है जो message छापकर मूल function चलाता है, और wrapper लौटा देता है।
def log_call(func):
def wrapper(*args, **kwargs):
print(f"→ calling {func.__name__} with {args} {kwargs}")
result = func(*args, **kwargs)
print(f"← {func.__name__} returned {result}")
return result
return wrapper
@log_call
def total_bill(price, qty, discount=0):
return price * qty - discount
total_bill(120, 3, discount=20)@log_call सिर्फ़ एक shortcut है। Python function बनाते ही असल में total_bill = log_call(total_bill) चलाता है।
*args, **kwargsसे wrapper कोई भी arguments ले सकता है, इसलिए एक decorator हर function पर चलता है।- Wrapper से हमेशा
return resultकरें – भूलने पर हर decorated functionNoneलौटाएगा।
Step 4: functools.wraps से नाम और docstring बचाएँ
साधारण wrapper मूल function का नाम और help text छिपा देता है। इससे debugging, documentation और Flask जैसे frameworks टूटते हैं जो function का नाम इस्तेमाल करते हैं। functools.wraps इन्हें copy कर देता है।
import functools
def plain(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def proper(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@plain
def fees_a():
"""Return the monthly fee."""
@proper
def fees_b():
"""Return the monthly fee."""
print(fees_a.__name__, "|", fees_a.__doc__)
print(fees_b.__name__, "|", fees_b.__doc__)हर wrapper पर @functools.wraps(func) लगाएँ। एक line लगती है और बाद के उलझाने वाले bugs रुकते हैं।
असली उदाहरण 1: function कितना समय लेता है
import functools, time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
ms = (time.perf_counter() - start) * 1000
print(f"{func.__name__} took {'under 50' if ms < 50 else 'over 50'} ms")
return result
return wrapper
@timer
def sum_of_squares(n):
return sum(i * i for i in range(n))
print(sum_of_squares(1000))Output हर computer पर एक जैसा रहे, इसलिए exact number की जगह “under/over 50 ms” छापा है; अपने code में f"{ms:.2f} ms" छापें।
असली उदाहरण 2: arguments लेने वाला decorator
Retry की संख्या जैसी settings देने के लिए एक और बाहरी level जोड़ें: एक function जो settings लेकर decorator लौटाए।
import functools
def retry(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except ConnectionError as err:
print(f"attempt {attempt} failed: {err}")
raise ConnectionError(f"{func.__name__} failed {times} times")
return wrapper
return decorator
calls = {"n": 0}
@retry(times=3)
def send_sms(phone):
calls["n"] += 1
if calls["n"] < 3: # fail twice, then succeed
raise ConnectionError("network busy")
return f"SMS sent to {phone}"
print(send_sms("98290xxxxx"))| Level | क्या लेता है | क्या लौटाता है |
|---|---|---|
retry(times) | settings | decorator |
decorator(func) | function | wrapper |
wrapper(*args) | call के arguments | result |
असली उदाहरण 3: सिर्फ़ logged-in admin को अनुमति
import functools
def require_role(role):
def decorator(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if user.get("role") != role:
return f"403 Forbidden: {user['name']} is not {role}"
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_role("admin")
def delete_student(user, roll_no):
return f"Student {roll_no} deleted by {user['name']}"
teacher = {"name": "Meena", "role": "teacher"}
admin = {"name": "Gagan", "role": "admin"}
print(delete_student(teacher, 21))
print(delete_student(admin, 21))Django का @login_required और Flask का @jwt_required ठीक ऐसे ही काम करते हैं: check आपके function से पहले चलता है, इसलिए भूलने का सवाल नहीं।
असली उदाहरण 4: lru_cache से built-in caching
from functools import lru_cache
calls = 0
@lru_cache(maxsize=None)
def fib(n):
global calls
calls += 1
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(35))
print("function calls:", calls)
print(fib.cache_info())Cache के बिना fib(35) लगभग 3 करोड़ calls करता है; cache के साथ सिर्फ़ 36। Caching सिर्फ़ pure functions पर लगाएँ जिनका result केवल arguments पर निर्भर हो।
कई decorators एक साथ: क्रम मायने रखता है
import functools
def bold(func):
@functools.wraps(func)
def wrapper():
return "<b>" + func() + "</b>"
return wrapper
def italic(func):
@functools.wraps(func)
def wrapper():
return "<i>" + func() + "</i>"
return wrapper
@bold
@italic
def title():
return "Result 2026"
print(title()) # same as bold(italic(title))Function के सबसे पास वाला decorator पहले लगता है। Stack को नीचे से ऊपर पढ़ें।
आम ग़लतियाँ
| ग़लती | क्या होता है | सुधार |
|---|---|---|
Wrapper में return result भूलना | Function None लौटाता है | मूल result लौटाएँ |
बिना arguments वाले decorator पर @timer() | TypeError: missing func | @timer लिखें; brackets सिर्फ़ decorator factory में |
functools.wraps नहीं | Logs में ग़लत नाम, Flask “endpoint overwriting” error | हमेशा @functools.wraps(func) |
| समय या database पढ़ने वाले function को cache करना | पुराने, ग़लत जवाब | सिर्फ़ pure functions cache करें |
अभ्यास
@count_callsलिखें जो बताए कि function कितनी बार चला।@validate_positiveलिखें जो कोई number argument negative होने परValueErrorदे।@cache_for(seconds)लिखें जो result सिर्फ़ दिए गए समय तक याद रखे (time.time()इस्तेमाल करें)।@timerऔर@log_callसाथ लगाकर छपे क्रम को समझाइए।
सारांश
- Decorator एक function लेकर extra काम वाला नया function लौटाता है।
- Function के ऊपर
@nameका मतलबfunc = name(func)। *args, **kwargsलें, result लौटाएँ औरfunctools.wrapsलगाएँ।- Settings चाहिए? एक बाहरी function जोड़ें – decorator factory।
- आम उपयोग: logging, timing, retries, permissions, caching, routing।
