AD

Python में Custom Decorator कैसे बनाएं: फंक्शन को मॉडिफाई किए बिना सुपरचार्ज करने की स्टेप-बाय-स्टेप गाइड

Python में Custom Decorator कैसे बनाएं: फंक्शन को मॉडिफाई किए बिना सुपरचार्ज करने की स्टेप-बाय-स्टेप गाइड

पायथन प्रोग्रामिंग में जब आपको कई अलग-अलग फंक्शन्स में एक ही तरह का लॉजिक लागू करना होता है—जैसे फंक्शन का एग्जीक्यूशन टाइम मापना, यूज़र ऑथेंटिकेशन चेक करना या नेटवर्क फेलियर पर कोड को दोबारा चलाना (Retry Mechanism)—तब हर बार कोड कॉपी-पेस्ट करना सबसे खराब प्रैक्टिस मानी जाती है। यहीं पर काम आता है पायथन का एक बेहद शक्तिशाली और एलिगेंट फीचर: Decorators।

डेकोरेटर मूल रूप से एक ऐसा फंक्शन होता है जो किसी दूसरे फंक्शन को इनपुट के तौर पर लेता है, उसके ऑरिजिनल कोड को बदले बिना उसकी फंक्शनैलिटी को बढ़ा देता है, और फिर अपडेटेड फंक्शन को रिटर्न करता है। इस प्रैक्टिकल हाउ-टू गाइड में हम शुरुआत से लेकर प्रोडक्शन-ग्रेड कस्टम डेकोरेटर बनाने का पूरा प्रोसेस कोड उदाहरणों के साथ सीखेंगे।

कस्टम डेकोरेटर बनाने का 5-स्टेप प्रैक्टिकल फ्रेमवर्क

पायथन में डेकोरेटर्स First-Class Functions के सिद्धांत पर काम करते हैं, जिसका मतलब है कि फंक्शन्स को वेरिएबल की तरह पास और रिटर्न किया जा सकता है। आइए स्टेप-बाय-स्टेप एक पावरफुल डेकोरेटर तैयार करते हैं।

स्टेप 1: बेसिक रैपर (Wrapper) फंक्शन की संरचना तैयार करें

डेकोरेटर बनाने के लिए हमें एक आउटर फंक्शन बनाना होता है, जिसके अंदर एक इनर रैपर फंक्शन मौजूद रहता है। इनर फंक्शन ही मूल फंक्शन के चलने से पहले और बाद में अतिरिक्त कोड रन करता है।

def my_decorator(func):
    def wrapper():
        print("फंक्शन कॉल होने से पहले का टास्क...")
        func()  # ऑरिजिनल फंक्शन को एग्जीक्यूट करना
        print("फंक्शन कॉल होने के बाद का टास्क...")
    return wrapper

स्टेप 2: आर्ग्युमेंट्स को हैंडल करने के लिए *args और **kwargs जोड़ें

अधिकतर फंक्शन्स पैरामीटर्स (आर्ग्युमेंट्स) स्वीकार करते हैं। अगर आपके इनर रैपर में *args और **kwargs नहीं होंगे, तो पैरामीटर वाला कोई भी फंक्शन एरर फेंक देगा।

def flexible_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"{func.__name__} फंक्शन इनपुट के साथ शुरू हो रहा है...")
        result = func(*args, **kwargs)
        return result  # रिटर्न वैल्यू भेजना न भूलें
    return wrapper

स्टेप 3: functools.wraps का उपयोग करके मेटाडेटा सुरक्षित करें

जब आप किसी फंक्शन को डेकोरेट करते हैं, तो पायथन डिफॉल्ट रूप से उसका नाम और डॉकस्ट्रिंग खो देता है और वह रैपर फंक्शन का नाम दिखाने लगता है। डिबगिंग में यह बहुत बड़ी सिरदर्दी बन सकता है। इसे ठीक करने के लिए हमेशा स्टैंडर्ड लाइब्रेरी के functools.wraps का उपयोग करें।

from functools import wraps

def clean_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

स्टेप 4: रियल-वर्ल्ड प्रोजेक्ट: Execution Timer डेकोरेटर तैयार करें

आइए एक ऐसा डेकोरेटर बनाएं जो किसी भी भारी कैलकुलेशन या API रिक्वेस्ट का सटीक रनटाइम मिलीसेकंड में नापे।

import time
from functools import wraps

def execution_timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        execution_time = (end_time - start_time) * 1000
        print(f"[PERF] {func.__name__} ने पूरा होने में {execution_time:.2f} ms का समय लिया।")
        return result
    return wrapper

स्टेप 5: डेकोरेटर को @ सिंटैक्स के साथ फंक्शन पर लागू करें

अब आप जिस भी फंक्शन की स्पीड नापना चाहते हैं, उसके ऊपर सिर्फ @execution_timer लिख दीजिए।

@execution_timer
def heavy_data_processing(n):
    total = sum(i * i for i in range(n))
    return total

# फंक्शन कॉल करें
output = heavy_data_processing(1000000)

एडवांस कॉन्सेप्ट: डेकोरेटर को अपने खुद के आर्ग्युमेंट्स कैसे दें?

कई बार आपको ऐसा डेकोरेटर चाहिए होता है जो खुद इनपुट वैल्यू ले सके, जैसे एक ऐसा Retry Decorator जिसमें आप तय कर सकें कि नेटवर्क फेल होने पर कितनी बार कोशिश करनी है। इसके लिए आपको 'थ्री-लेयर नेस्टिंग' का उपयोग करना होता है:

import time
from functools import wraps

def retry(max_attempts=3, delay_seconds=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    print(f"एरर: {e}। प्रयास {attempts}/{max_attempts}। {delay_seconds}s में रीट्राय...")
                    time.sleep(delay_seconds)
            raise Exception(f"{func.__name__} सभी {max_attempts} प्रयासों के बाद भी फेल हो गया।")
        return wrapper
    return decorator

# उपयोग करने का तरीका:
@retry(max_attempts=4, delay_seconds=2)
def fetch_remote_api():
    # मान लीजिए यह नेटवर्क कॉल कभी-कभी फेल हो जाती है
    import random
    if random.choice([True, False]):
        raise ConnectionError("सर्वर अनरीचेबल!")
    return "डेटा सफलतापूर्वक प्राप्त हुआ"

डेकोरेटर बनाते समय होने वाली 4 आम गलतियां

  • फंक्शन का रिटर्न वैल्यू पास न करना: रैपर फंक्शन के अंदर केवल func(*args, **kwargs) लिख देने से ऑरिजिनल फंक्शन का आउटपुट खो जाता है। हमेशा return result लिखें।
  • @wraps छोड़ देना: अगर आप @wraps(func) नहीं लगाते हैं, तो function.__name__ हमेशा 'wrapper' दिखाएगा, जिससे यूनिट टेस्टिंग और लॉगिंग टूल्स भ्रमित हो जाते हैं।
  • चेनिंग (Chaining) का क्रम न समझना: जब आप एक साथ कई डेकोरेटर्स लगाते हैं, तो वे नीचे से ऊपर (Bottom to Top) की तरफ एग्जीक्यूट होते हैं। क्रम गलत होने पर ऑथेंटिकेशन या कैशिंग का लॉजिक टूट सकता है।
  • म्यूटेबल डिफॉल्ट आर्ग्युमेंट्स: अगर डेकोरेटर में डिफॉल्ट वैल्यू के तौर पर लिस्ट या डिक्शनरी दी जाए, तो वह सभी फंक्शन कॉल्स के बीच शेयर हो जाती है और अनचाहे बग्स पैदा करती है।

डेकोरेटर्स के लिए बेस्ट प्रैक्टिसेज और प्रो टिप्स

  • सिंगल रिस्पॉन्सिबिलिटी: एक डेकोरेटर को केवल एक ही काम करने दें। टाइमिंग, ऑथेंटिकेशन और कैशिंग के लिए अलग-अलग डेकोरेटर्स बनाएं और जरूरत पड़ने पर उन्हें स्टैक (चेन) करें।
  • Class-Based Decorators: अगर आपके डेकोरेटर को बहुत ज्यादा स्टेट (State) स्टोर करनी है, तो फंक्शन की जगह क्लास बनाएं और उसमें __call__ मेथड को डिफाइन करें।
  • साइड इफेक्ट्स से बचें: रैपर फंक्शन के बाहर सीधे कोड न लिखें, क्योंकि वह कोड फंक्शन कॉल के समय नहीं बल्कि मॉड्यूल इम्पोर्ट होते ही रन हो जाता है।

अक्सर पूछे जाने वाले सवाल (FAQ)

1. क्या एक फंक्शन पर एक से ज्यादा डेकोरेटर्स लगाए जा सकते हैं?

हाँ, आप एक फंक्शन के ऊपर एक से अधिक डेकोरेटर्स लगा सकते हैं। इसे 'Decorator Stacking' कहा जाता है। याद रखें कि जो डेकोरेटर फंक्शन के सबसे नजदीक (नीचे) होगा, वह सबसे पहले रन होगा।

2. functools.wraps का उपयोग करना अनिवार्य क्यों माना जाता है?

यह अनिवार्य नहीं है लेकिन एक इंडस्ट्री-स्टैंडर्ड बेस्ट प्रैक्टिस है। बिना इसके आपके फंक्शन का मूल नाम (__name__), डॉक्युमेंटेशन स्ट्रिंग (__doc__) और एनोटेशन 'wrapper' फंक्शन के मेटाडेटा से ओवरराइट हो जाते हैं।

3. क्या डेकोरेटर्स का उपयोग क्लास मेथड्स पर भी किया जा सकता है?

बिल्कुल। डेकोरेटर्स रेगुलर फंक्शन्स और क्लास मेथड्स दोनों पर काम करते हैं। क्लास मेथड के मामले में पहला पैरामीटर self होता है, जिसे आपका *args आसानी से हैंडल कर लेता है।

4. क्या डेकोरेटर के इस्तेमाल से कोड की स्पीड धीमी हो जाती है?

डेकोरेटर एक अतिरिक्त फंक्शन कॉल ओवरहेड जोड़ता है, लेकिन यह प्रभाव नैनोसेकंड्स में होता है। सामान्य एप्लिकेशन्स और डेटा पाइपलाइन्स में यह अंतर नगण्य होता है, जबकि इससे मिलने वाली कोड रीडेबिलिटी और सुरक्षा कहीं अधिक महत्वपूर्ण है।

Post a Comment

0 Comments