# Python में डेकोरेटर: ये क्या हैं, कैसे काम करते हैं और अपना कैसे लिखें

URL: https://skillaria.top/hi/guides/python/dekoratory-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
```

इस ढाँचे के तीन नियम हैं:

1. **`*args, **kwargs`** — रैपर कोई भी आर्गुमेंट लेता है और उन्हें बिना बदले आगे भेजता है।
2. **`return result`** — परिणाम लौटाना न भूलें, वरना सजाया गया फ़ंक्शन हमेशा `None` देगा।
3. **`@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': }
```

उल्टी स्थिति — क्लास डेकोरेटर की भूमिका में। कोई भी ऑब्जेक्ट जिसमें `__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 कोर्स में व्यवहार में सिखाए जाते हैं।

## FAQ

**क्या सजाए गए फ़ंक्शन से डेकोरेटर हटाया जा सकता है?**

अगर डेकोरेटर functools.wraps इस्तेमाल करता है, तो मूल फ़ंक्शन __wrapped__ एट्रिब्यूट से मिलता है: func.__wrapped__(...) बिना रैपर के चलेगा। नहीं तो कोड से @ वाली पंक्ति हटानी होगी।

**डेकोरेटर का कोड कब चलता है?**

डेकोरेटर की अपनी बॉडी फ़ंक्शन घोषित होते समय, आम तौर पर मॉड्यूल इम्पोर्ट के दौरान, एक बार चलती है। wrapper के भीतर का कोड सजाए गए फ़ंक्शन की हर कॉल पर चलता है।

**क्या क्लास के मेथड पर डेकोरेटर लगाया जा सकता है?**

हाँ, *args, **kwargs वाला सामान्य डेकोरेटर मेथड पर भी चलता है: self बस args का पहला तत्व होगा।

**डेकोरेटर और इनहेरिटेंस में क्या फ़र्क है?**

इनहेरिटेंस क्लास के व्यवहार को पदानुक्रम से बदलता है, जबकि डेकोरेटर किसी फ़ंक्शन या क्लास को बाहर से लपेटता है और बिना कोड बदले दूसरे डेकोरेटरों के साथ जुड़ जाता है।


---
Source: Skillaria.Top (https://skillaria.top)
