Python में डेकोरेटर: ये क्या हैं, कैसे काम करते हैं और अपना कैसे लिखें
Python में डेकोरेटर एक फ़ंक्शन है, जो दूसरे फ़ंक्शन को लेकर नया फ़ंक्शन लौटाता है, आम तौर पर एक रैपर, जो कॉल से पहले या बाद में व्यवहार जोड़ता है: लॉगिंग, समय मापना, कैश, अनुमति की जाँच। @decorator लिखना func = decorator(func) का संक्षिप्त रूप है; रैपर को मूल फ़ंक्शन का नाम और docstring देने के लिए functools.wraps इस्तेमाल होता है।
Python में डेकोरेटर एक फ़ंक्शन है, जो दूसरे फ़ंक्शन को लेता है और एक नया फ़ंक्शन लौटाता है, आम तौर पर मूल फ़ंक्शन के चारों ओर एक रैपर। इससे फ़ंक्शन में नया व्यवहार जुड़ता है — लॉगिंग, समय मापना, कैश, दोबारा कोशिश — बिना उसका कोड बदले। डेकोरेटर @नाम लिखकर def के ऊपर लगाए जाते हैं, और ये Flask, FastAPI, pytest और मानक लाइब्रेरी में हर जगह मिलते हैं।
डेकोरेटर सरल भाषा में
उपहार और उसके ऊपर लिपटे रैपिंग पेपर की कल्पना कीजिए। उपहार (फ़ंक्शन) वही रहता है, पर बाहर एक परत जुड़ जाती है, जो सबसे पहले चलती है। डेकोरेटर ऐसी ही पैकिंग है: कॉल होने पर पहले रैपर का कोड चलता है, वह मूल फ़ंक्शन को बुलाता है और परिणाम के साथ कुछ भी कर सकता है।
यह इसलिए संभव है क्योंकि Python में फ़ंक्शन साधारण ऑब्जेक्ट हैं। उन्हें दूसरे फ़ंक्शन को दिया जा सकता है, फ़ंक्शन से लौटाया जा सकता है और फ़ंक्शन के भीतर घोषित किया जा सकता है। डेकोरेटर इन्हीं तीन गुणों पर टिके हैं।
डेकोरेटर कैसे काम करता है: @ सिंटैक्टिक शुगर है
एक सरल डेकोरेटर लेते हैं, जो कॉल से पहले और बाद में संदेश छापता है:
def log_calls(func):
def wrapper():
print(f"{func.__name__} को बुला रहे हैं")
result = func()
print(f"{func.__name__} पूरा हुआ")
return result
return wrapper
@log_calls
def hello():
print("नमस्ते!")
hello()
hello को बुला रहे हैं
नमस्ते!
hello पूरा हुआ
def के ऊपर @log_calls लिखना उस पंक्ति के बिल्कुल बराबर है, जो घोषणा के बाद लिखी जाती है:
def hello():
print("नमस्ते!")
hello = log_calls(hello)
अब नाम hello wrapper की ओर इशारा करता है, और मूल फ़ंक्शन क्लोज़र के भीतर रहता है — wrapper बाहरी फ़ंक्शन का वेरिएबल func याद रखता है। एक ज़रूरी बात: डेकोरेटर एक बार लगता है, यानी def चलने के समय (मॉड्यूल इम्पोर्ट होने पर), जबकि रैपर हर कॉल पर चलता है।
क्लोज़र और डेकोरेटर
क्लोज़र एक भीतरी फ़ंक्शन है, जो बाहरी फ़ंक्शन ख़त्म होने के बाद भी उसके वेरिएबल याद रखता है। क्लोज़र के बिना डेकोरेटर संभव नहीं: इसी से wrapper को func मिलता है। क्लोज़र में स्थिति (state) भी रखी जा सकती है, जैसे कॉल की गिनती:
def count_calls(func):
calls = 0
def wrapper(*args, **kwargs):
nonlocal calls
calls += 1
print(f"{func.__name__}: कॉल नंबर {calls}")
return func(*args, **kwargs)
return wrapper
Python में अपना डेकोरेटर कैसे लिखें: सामान्य ढाँचा
पहले उदाहरण में दो कमियाँ हैं: रैपर कोई आर्गुमेंट नहीं लेता, और वह मूल फ़ंक्शन का नाम «निगल» जाता है। हर डेकोरेटर इस ढाँचे से शुरू करना चाहिए:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# कॉल से पहले का कोड
result = func(*args, **kwargs)
# कॉल के बाद का कोड
return result
return wrapper
इस ढाँचे के तीन नियम हैं:
*args, **kwargs— रैपर कोई भी आर्गुमेंट लेता है और उन्हें बिना बदले आगे भेजता है।return result— परिणाम लौटाना न भूलें, वरना सजाया गया फ़ंक्शन हमेशाNoneदेगा।@functools.wraps(func)— मूल फ़ंक्शन की मेटाडेटा रैपर पर लाता है।
एक उदाहरण — कार्य में लगा समय मापना:
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} ने {elapsed:.3f} सेकंड लिए")
return result
return wrapper
@timer
def slow_sum(n):
return sum(range(n))
slow_sum(10_000_000) # slow_sum ने 0.142 सेकंड लिए
functools.wraps क्यों ज़रूरी है
wraps के बिना सजाए गए फ़ंक्शन का नाम, docstring और दूसरे गुण बदल जाते हैं — बाहर से wrapper दिखता है:
def plain(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@plain
def add(a, b):
"""दो संख्याएँ जोड़ता है।"""
return a + b
print(add.__name__, add.__doc__) # wrapper None
@functools.wraps(func) के साथ रैपर को मूल का __name__, __doc__, __module__, __qualname__, __annotations__ मिलता है, और __wrapped__ में मूल फ़ंक्शन का संदर्भ रहता है। यह डीबगिंग और ट्रेसबैक के लिए, help() के लिए, उन फ़्रेमवर्क के लिए जो फ़ंक्शन को नाम से रजिस्टर करते हैं, और inspect.signature के लिए ज़रूरी है, जो असली सिग्नेचर दिखाता है।
पैरामीटर वाला डेकोरेटर (आर्गुमेंट के साथ)
कभी-कभी डेकोरेटर को सेटिंग्स चाहिए: @retry(times=3), @app.route("/")। इसके लिए एक और नेस्टिंग की परत जोड़ी जाती है। बाहरी फ़ंक्शन पैरामीटर लेता है और असली डेकोरेटर लौटाता है — इस ढाँचे को डेकोरेटर फ़ैक्टरी कहते हैं।
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def ping():
print("ping")
ping() # ping, ping, ping — तीन पंक्तियाँ
कॉल का क्रम: repeat(times=3) decorator लौटाता है, वह ping पर लगता है और wrapper लौटाता है। यानी @repeat(times=3) का मतलब है ping = repeat(times=3)(ping)।
व्यावहारिक उदाहरण — त्रुटि होने पर दोबारा कोशिश:
import functools
import time
def retry(times=3, delay=1.0, exceptions=(Exception,)):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except exceptions:
if attempt == times:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=5, delay=0.5, exceptions=(ConnectionError,))
def fetch_rate():
...
एक ही फ़ंक्शन पर कई डेकोरेटर
डेकोरेटर एक के ऊपर एक लगाए जा सकते हैं। वे नीचे से ऊपर लगते हैं: def के सबसे पास वाला पहले।
@timer
@log_calls
def report():
...
# यह वही है:
report = timer(log_calls(report))
कॉल के समय बाहरी परत (timer) पहले चलती है, इसलिए वह log_calls के काम का समय भी मापेगी। क्रम मायने रखता है: अगर एक डेकोरेटर अनुमति जाँचता है और दूसरा परिणाम कैश करता है, तो दोनों का क्रम बदलने से व्यवहार बदल जाता है।
Python के मानक और अंतर्निहित डेकोरेटर
कई उपयोगी डेकोरेटर भाषा और मानक लाइब्रेरी में पहले से हैं:
| डेकोरेटर | क्या करता है |
|---|---|
@property | मेथड को केवल-पढ़ने वाले एट्रिब्यूट में बदलता है; @x.setter के साथ नियंत्रित एट्रिब्यूट बनता है |
@staticmethod | बिना self का क्लास-मेथड, जो क्लास के नामस्थान में साधारण फ़ंक्शन है |
@classmethod | मेथड को इंस्टेंस की जगह क्लास cls मिलती है — वैकल्पिक कंस्ट्रक्टर इसी से बनते हैं |
@functools.cache, @functools.lru_cache | आर्गुमेंट के अनुसार परिणाम कैश करते हैं |
@functools.wraps | मूल फ़ंक्शन की मेटाडेटा रैपर पर कॉपी करता है |
@dataclasses.dataclass | क्लास के लिए __init__, __repr__, __eq__ बनाता है |
@contextlib.contextmanager | जनरेटर से with के लिए कॉन्टेक्स्ट मैनेजर बनाता है |
कैशिंग: functools.cache और lru_cache
कैश डेकोरेटर हर आर्गुमेंट-सेट का परिणाम याद रखता है और दोबारा कॉल होने पर बिना गणना के लौटा देता है। इसका क्लासिक उदाहरण है रिकर्सिव फ़िबोनाची, जो कैश के बिना तेज़ी से बढ़ते समय में चलती है:
import functools
@functools.cache
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(100)) # 354224848179261915075
functools.cache (Python 3.9 में आया) सारे परिणाम बिना सीमा के रखता है। @functools.lru_cache(maxsize=128) सिर्फ़ ताज़ा प्रविष्टियाँ रखता है और पुरानी हटा देता है — लंबे समय तक चलने वाली सेवाओं के लिए यह सुरक्षित है। आँकड़े fib.cache_info() से दिखते हैं, और fib.cache_clear() से कैश खाली होता है। ऐसे फ़ंक्शन के आर्गुमेंट हैश होने योग्य होने चाहिए: लिस्ट आर्गुमेंट TypeError: unhashable type: 'list' देगा। और कैश सिर्फ़ शुद्ध (pure) फ़ंक्शन पर लगाएँ — जो एक जैसे आर्गुमेंट पर हमेशा एक ही परिणाम दें और कोई बाहरी असर न छोड़ें।
property डेकोरेटर क्या करता है
@property से मेथड को बिना कोष्ठक के एट्रिब्यूट की तरह पढ़ा जा सकता है, और ज़रूरत हो तो असाइनमेंट पर नियंत्रण रखा जा सकता है:
class Account:
def __init__(self, balance):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("बैलेंस ऋणात्मक नहीं हो सकता")
self._balance = value
acc = Account(100)
print(acc.balance) # 100
acc.balance = -5 # ValueError
staticmethod और classmethod
class Money:
def __init__(self, kopecks):
self.kopecks = kopecks
@classmethod
def from_rubles(cls, rubles):
return cls(round(rubles * 100))
@staticmethod
def is_valid(value):
return value >= 0
m = Money.from_rubles(12.5)
print(m.kopecks, Money.is_valid(-1)) # 1250 False
ठीक इसी कारण @classmethod मेथड को इंस्टेंस की जगह स्वयं क्लास पर काम करने देता है: from_rubles सही तरीके से सबक्लास का ऑब्जेक्ट भी बनाएगा।
क्लास पर डेकोरेटर और डेकोरेटर के रूप में क्लास
डेकोरेटर को क्लास पर भी लगाया जा सकता है: उसे क्लास मिलती है और वह क्लास लौटाता है (वही या बदली हुई)। @dataclass ऐसे ही काम करता है। एक सरल उदाहरण — क्लासों का रजिस्टर:
registry = {}
def register(cls):
registry[cls.__name__] = cls
return cls
@register
class CsvExporter:
pass
print(registry) # {'CsvExporter': <class '__main__.CsvExporter'>}
उल्टी स्थिति — क्लास डेकोरेटर की भूमिका में। कोई भी ऑब्जेक्ट जिसमें __call__ मेथड हो, काम करेगा; स्थिति को इंस्टेंस एट्रिब्यूट में रखना सुविधाजनक है:
import functools
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs):
self.calls += 1
return self.func(*args, **kwargs)
@CountCalls
def tick():
pass
tick(); tick()
print(tick.calls) # 2
असिंक्रोनस डेकोरेटर
async def फ़ंक्शन के लिए रैपर भी कोरूटीन होना चाहिए और परिणाम await से लेना चाहिए, वरना बिना चले कोरूटीन ऑब्जेक्ट लौटेगा:
import functools
def log_async(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
print(f"शुरू {func.__name__}")
result = await func(*args, **kwargs)
print(f"तैयार {func.__name__}")
return result
return wrapper
Python में डेकोरेटर किसलिए
- सामान्य तर्क (cross-cutting) — लॉगिंग, मेट्रिक्स, समय मापना, हर फ़ंक्शन में कोड कॉपी किए बिना।
- कैशिंग — महंगी शुद्ध गणनाओं के लिए
@functools.cache। - जाँच — अनुमति, आर्गुमेंट का सत्यापन, वेब हैंडलर में प्रमाणीकरण।
- रजिस्ट्रेशन — Flask और FastAPI के रूट (
@app.get("/")), pytest के फ़िक्स्चर। - भरोसेमंदी — दोबारा कोशिश, टाइमआउट, त्रुटि संभालना।
सबसे बड़ा फ़ायदा है ज़िम्मेदारियों का बँटवारा: फ़ंक्शन अपना काम करता है, और सहायक व्यवहार एक पंक्ति से जुड़ता है और उतनी ही आसानी से हटाया भी जा सकता है।
आम गलतियाँ
- रैपर परिणाम नहीं लौटाता — सजाए गए फ़ंक्शन से
Noneमिलता है। *args, **kwargsनहीं लिखे — आर्गुमेंट के साथ कॉल परTypeError।wrapsभूल गए — गलत__name__और docstring, लॉग में उलझन।- फ़ैक्टरी में कोष्ठक भूल गए —
@repeatकी जगह@repeat(3)चाहिए, वरना फ़ंक्शन हीtimesमें चला जाएगा। - डेकोरेटर के भीतर भारी कोड — वह मॉड्यूल इम्पोर्ट होते समय चलता है, कॉल के समय नहीं।
निष्कर्ष
Python के डेकोरेटर हायर-ऑर्डर फ़ंक्शन हैं: वे फ़ंक्शन लेते हैं, रैपर लौटाते हैं, और @ सिर्फ़ असाइनमेंट का छोटा रूप है। शुरुआत *args, **kwargs, return result और functools.wraps वाले ढाँचे से करें, और सेटिंग्स के लिए फ़ैक्टरी की परत जोड़ें। जिन फ़ंक्शनों, क्लोज़रों और एनोटेशन पर डेकोरेटर टिके हैं, वे Python कोर्स में व्यवहार में सिखाए जाते हैं।
अक्सर पूछे जाने वाले प्रश्न
क्या सजाए गए फ़ंक्शन से डेकोरेटर हटाया जा सकता है?
अगर डेकोरेटर functools.wraps इस्तेमाल करता है, तो मूल फ़ंक्शन __wrapped__ एट्रिब्यूट से मिलता है: func.__wrapped__(...) बिना रैपर के चलेगा। नहीं तो कोड से @ वाली पंक्ति हटानी होगी।
डेकोरेटर का कोड कब चलता है?
डेकोरेटर की अपनी बॉडी फ़ंक्शन घोषित होते समय, आम तौर पर मॉड्यूल इम्पोर्ट के दौरान, एक बार चलती है। wrapper के भीतर का कोड सजाए गए फ़ंक्शन की हर कॉल पर चलता है।
क्या क्लास के मेथड पर डेकोरेटर लगाया जा सकता है?
हाँ, *args, **kwargs वाला सामान्य डेकोरेटर मेथड पर भी चलता है: self बस args का पहला तत्व होगा।
डेकोरेटर और इनहेरिटेंस में क्या फ़र्क है?
इनहेरिटेंस क्लास के व्यवहार को पदानुक्रम से बदलता है, जबकि डेकोरेटर किसी फ़ंक्शन या क्लास को बाहर से लपेटता है और बिना कोड बदले दूसरे डेकोरेटरों के साथ जुड़ जाता है।
यह विषय “Python 3.15: от новичка до профи” कोर्स में अभ्यास के साथ विस्तार से समझाया गया है।
कोर्स पर जाएँ