2025, Oct 04 03:30

Python में डेकोरेटर को सही तरह से संयोजित करें: कॉल-टाइम रैपिंग से बचें

Python में डेकोरेटर संयोजन का सही तरीका: डेकोरेशन-टाइम पर रैप करें ताकि nonlocal क्लोज़र स्टेट बना रहे. कॉल-टाइम रैपिंग से होने वाले स्टेट रीसेट से बचें.

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

समस्या को पुनः उत्पन्न करना

नीचे एक न्यूनतम उदाहरण है, जहाँ चाइल्ड डेकोरेटर एक पैरेंट डेकोरेटर पर निर्भर करता है जो nonlocal टॉगल रखता है। उद्देश्य पहली कॉल और बाद की कॉलों पर अलग लॉजिक चलाना है, लेकिन व्यवहार कभी भी “after” शाखा तक नहीं पहुँचता।

def base_decorator(fn):
primed = False
def inner(*a, **kw):
nonlocal primed
if primed:
print("executing after first call")
return fn(*a, **kw)
else:
print("executing before first call")
primed = True
out = fn(*a, **kw)
return out
return inner

def should_allow():
return True

def child_decorator(fn):
def inner(*a, **kw):
if should_allow():
composed = base_decorator(fn)
return composed(*a, **kw)
else:
print("do nothing")
return inner

class Runner:
def __init__(self):
print("init called")

@child_decorator
def run(self):
print("Hello from run")

obj = Runner()
obj.run()
obj.run()

अंदर क्या हो रहा है

nonlocal चर पैरेंट डेकोरेटर द्वारा बनाए गए क्लोज़र के भीतर रहता है। ऊपर दिए गए कोड में, चाइल्ड डेकोरेटर रैप किए गए फ़ंक्शन के भीतर पैरेंट डेकोरेटर को कॉल करता है। इसका मतलब है कि हर इनवोकेशन पर नया क्लोज़र बनता है। हर कॉल एक नई nonlocal स्टेट से शुरू होती है, इसलिए जाँच बार-बार “before” शाखा पर ही अटक जाती है और “after” तक नहीं पहुँचती।

समाधान

रैपिंग कॉल के समय नहीं, डेकोरेशन के समय एक बार करें। पैरेंट डेकोरेटर को कॉल inner फ़ंक्शन के बाहर ले जाएँ, ताकि क्लोज़र (और उसका nonlocal फ़्लैग) केवल एक बार बने और सभी कॉलों के बीच बना रहे।

def child_decorator(fn):
composed = base_decorator(fn)
def inner(*a, **kw):
if should_allow():
return composed(*a, **kw)
else:
print("do nothing")
return inner

इस बदलाव के साथ, पहली कॉल “before” शाखा का इस्तेमाल करती है और फ़्लैग पलट देती है, जबकि बाद की कॉलें “after” शाखा में पहुँचती हैं—ठीक इरादे के मुताबिक। पैरेंट डेकोरेटर ज्यों का त्यों रहता है।

यह क्यों महत्वपूर्ण है

डेकोरेटर का व्यवहार इस बात पर निर्भर करता है कि रैपिंग कब होती है। अगर आप कॉल पाथ के भीतर दोबारा रैप करते हैं, तो आप पूरा क्लोज़र स्टेट फिर से बना देते हैं—उन nonlocal वेरिएबल्स सहित जो पहली बार चलने या मेमोइज़्ड स्टेट को ट्रैक करते हैं। जिन डेकोरेटरों का काम initialization, gating या one-time setup का समन्वय है, उनके लिए यह फर्क बेहद अहम है, ताकि सूक्ष्म बग और दोहराया गया काम टाला जा सके।

मुख्य बातें

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

यह लेख StackOverflow पर प्रश्न (लेखक: Rohit Pathak) और Abdul Aziz Barkat के उत्तर पर आधारित है।