2025, Oct 03 03:32

Python asyncio में await को iterator क्यों चाहिए: जनरेटर और yield की भूमिका

जानें क्यों Python asyncio में await को iterator लौटाने वाला __await__ चाहिए, yield/जनरेटर से cooperative multitasking कैसे काम करती है, और यह कब फ़ायदेमंद है।

Python asyncio में await को iterator क्यों चाहिए

Python के asyncio को सीखते समय अक्सर यह सवाल अटकाता है कि await सिर्फ उन्हीं awaitable ऑब्जेक्ट्स को क्यों स्वीकार करता है जो __await__ मेथड उपलब्ध कराते हैं और वह मेथड एक iterator लौटाता है। इसका संक्षिप्त जवाब यह है कि Python का async मॉडल जनरेटर के ऊपर बना cooperative multitasking है, और yield वही तंत्र है जो निष्पादन को रोकता व दोबारा चालू करता है। Iterator इंटरफेस ही वह रूप है जिसमें यह 'रुको-फिर-चलो' व्यवहार Python कोड में व्यक्त होता है।

वह न्यूनतम उदाहरण जो सवाल को सामने लाता है

async def runner():
    data = await rendezvous
    print(data)
class Rendezvous:
    def __await__(self):
        yield "Hello"
        return "World"
rendezvous = Rendezvous()
it = runner().__await__()
first_step = next(it)

runner() कॉल करने पर आपको एक coroutine ऑब्जेक्ट मिलता है। जब coroutine await rendezvous पर पहुँचता है, तो Python अपेक्षा करता है कि rendezvous awaitable हो। Python की भाषा में, इसका मतलब है कि वह __await__() उपलब्ध कराता है और यह मेथड एक iterator लौटाता है। अंदरूनी तौर पर, await rendezvous का अर्थ है rendezvous.__await__() को बुलाना और लौटे हुए iterator पर iteration करना।

असल में होता क्या है

Async cooperative multitasking है। एक समय में सिर्फ एक ही कोड का भाग चलता है। प्रगति तभी होती है जब कोई हिस्सा स्पष्ट रूप से नियंत्रण छोड़ देता है—यानी yield करता है। जब कोई टास्क yield करता है, तो इवेंट लूप किसी दूसरे टास्क को आगे बढ़ा सकता है और बाद में पहले वाले पर लौट आता है। यह समानांतर CPU निष्पादन नहीं है; यह उस कोड को रोकने के बारे में है जो प्रतीक्षा कर रहा है, ताकि इस बीच अन्य कोड चल सके।

यह फर्क अहम है, क्योंकि जो Python कोड पूरी तरह CPU-बाउंड है, उसे टास्क के बीच कूदने से लाभ नहीं मिलता। यदि काम सिर्फ गणना का है, तो उसे कई coroutines में बाँटना अक्सर केवल कॉन्टेक्स्ट‑स्विचिंग का ओवरहेड बढ़ाता है। Async तब चमकता है जब काम CPU‑बाउंड नहीं होता—जैसे नेटवर्क I/O या डेटाबेस क्वेरी। किसी बाहरी सिस्टम को अनुरोध भेजने के बाद, जवाब आने तक उस टास्क के भीतर Python के पास करने लायक कुछ नहीं होता—CPU के पैमाने पर यह बहुत लम्बा इंतज़ार है। उस खाली समय में अन्य टास्क चल सकते हैं।

Python कोड में यह “यहाँ रुको, बाद में फिर शुरू करो” कैसे व्यक्त करें? इसकी मूल इकाई yield है। जनरेटर वही pause‑और‑resume का अर्थशास्त्र देते हैं जिसकी इवेंट लूप को ज़रूरत होती है। यही वजह है कि await प्रोटोकॉल को iterator के रूप में परिभाषित किया गया है: __await__ को ऐसा मान लौटाना चाहिए जिस पर रनटाइम iterate कर सके, और व्यवहार में यह yield बिंदुओं द्वारा संचालित होता है।

क्यों __await__ एक iterator लौटाता है

Iterator की शर्त इसलिए है क्योंकि नियंत्रण छोड़ना Python कोड में yield के ज़रिए ही होता है। इवेंट लूप तय करता है कि किसे चलना है और किसी yielded चीज़ को कब फिर से शुरू करना है। शुद्ध Python में, निष्पादन रोकने और उसी स्थान से आगे बढ़ने का तंत्र yield ही है। इसलिए जनरेटर/इटरेटर प्रोटोकॉल “awaitable प्रगति” का स्वाभाविक और उपलब्ध निरूपण है।

एक व्यावहारिक सीमा भी है। जिन असली async प्रिमिटिव्स के साथ आप काम करते हैं—नेटवर्क सॉकेट्स से लेकर डेटाबेस ड्राइवर तक—वे अधिकतर निचले स्तर की C लाइब्रेरीज़ में लिखे होते हैं। आप इन्हें शुद्ध Python में नहीं लिखते, बल्कि इस्तेमाल करते हैं। asyncio मॉड्यूल इनमें से कुछ प्रिमिटिव्स को उजागर करता है ताकि आप इन्हें ऑर्केस्ट्रेट कर सकें। अगर आपको Python‑स्तर पर awaitable व्यवहार चाहिए, तो __await__ वही हुक है जो इवेंट लूप के साथ एकीकरण कराता है—Python में pause करने में सक्षम एकमात्र निर्माण yield के जरिए।

अंदर की प्रक्रिया: iteration कैसे await को चलाती है

जब इंटरप्रेटर await obj चलाता है, वह obj.__await__() कॉल करके एक iterator प्राप्त करता है। उस iterator को आगे बढ़ाने से awaitable नियंत्रण छोड़ देता है। नियंत्रण छोड़ते ही इवेंट लूप कुछ और चला सकता है। बाद में, जब फिर शुरू करने का समय आता है, तो iteration पिछले yield वाले बिंदु से जारी होती है और अंततः iterator पूरा होते ही परिणाम मिलता है। जनरेटर‑आधारित iterator ही वह pause/resume जीवनचक्र समाहित करता है जिसकी इवेंट लूप को ज़रूरत होती है।

क्या await “किसी भी” ऑब्जेक्ट को स्वीकार कर सकता है?

Python की संहिता में कोड को रोकने और फिर शुरू करने का कोई तरीका चाहिए। Yield वही निर्माण है जो यह करता है, और जनरेटर iterator प्रोटोकॉल के माध्यम से yield को उजागर करते हैं। इसी कारण await को इस रूप में परिभाषित किया गया है कि __await__ एक iterator लौटाए—न कि ऐसे मनमाने ऑब्जेक्ट स्वीकार किए जाएँ जिनमें yield करने का कोई तरीका ही न हो।

एक व्यावहारिक पैटर्न: Python‑स्तर के रैपर awaitables

आपके अधिकांश awaitables निचले स्तर के C मॉड्यूल्स से आते हैं। यदि आप Python में async‑जैसा व्यवहार उपलब्ध कराना चाहते हैं और कॉलर्स को लौटाने से पहले परिणामों को रूपांतरित करना चाहते हैं, तो __await__ ही तरीका है। ठहराव के बिंदु yield से ही व्यक्त करें, क्योंकि इंतज़ार करते हुए इसी तरह आप अन्य कामों को चलने देते हैं।

async for record in low_level_stream:
    yield Wrapped(record)

यह मंतव्य दिखाता है: आप किसी निचले स्तर के async परिणाम का इंतज़ार करते हैं और अनुकूलित मानों को yield करते हैं। मूल बात वही है—yield करना ही आपके कोड को रोकता है और इवेंट लूप को कामों को आपस में पिरोने देता है।

यह क्यों मायने रखता है

यह समझना कि Python में async cooperative है, सिस्टम की रूपरेखा और निदान का तरीका बदल देता है। यदि कोड कभी yield ही नहीं करता, तो कुछ और चल ही नहीं पाएगा। यदि काम CPU‑बाउंड है, तो coroutines के बीच स्विच करने से वह तेज़ी से पूरा नहीं होगा। और यदि आपको Python‑स्तर के awaitables चाहिए, तो __await__ के साथ yield ही इवेंट लूप से जुड़ने का बिंदु है।

निष्कर्ष

await उन्हीं awaitables पर काम करता है जो __await__ के जरिए एक iterator उपलब्ध कराते हैं, क्योंकि जनरेटर और उनका yield ही वह तरीका है जिससे Python कोड रुक और फिर चल सकता है। इवेंट लूप इसी iterator का उपयोग करके निष्पादन का समन्वय करता है। ज़्यादातर वास्तविक async प्रिमिटिव्स C में हैं, और asyncio उनमें से कुछ को उजागर करता है ताकि आप I/O और अन्य non‑CPU‑bound कार्यों का कुशल ऑर्केस्ट्रेशन कर सकें। जब आपको Python में async परिणामों को रूप देने या अनुकूलित करने की ज़रूरत हो, __await__ वह हुक है, और yield वह तंत्र जो cooperative multitasking को संभव बनाता है।

यह लेख StackOverflow के प्रश्न (द्वारा Meet Patel) और deceze के उत्तर पर आधारित है।