पायथन के इटेशन की शक्ति को अनलॉक करें। __iter__ और __next__ विधियों का उपयोग करके कस्टम इटरेटर को लागू करने पर वैश्विक डेवलपर्स के लिए एक व्यापक मार्गदर्शिका, व्यावहारिक, वास्तविक दुनिया के उदाहरणों के साथ।
पायथन के इटरेटर प्रोटोकॉल को समझना: __iter__ और __next__ में गहराई से उतरें
इटेशन प्रोग्रामिंग में सबसे मूलभूत अवधारणाओं में से एक है। पायथन में, यह सुरुचिपूर्ण और कुशल तंत्र है जो सरल for लूप से लेकर जटिल डेटा प्रोसेसिंग पाइपलाइन तक सब कुछ संचालित करता है। आप हर दिन इसका उपयोग करते हैं जब आप किसी सूची के माध्यम से लूप करते हैं, किसी फ़ाइल से लाइनें पढ़ते हैं, या डेटाबेस परिणामों के साथ काम करते हैं। लेकिन क्या आपने कभी सोचा है कि पर्दे के पीछे क्या हो रहा है? पायथन को यह कैसे पता चलता है कि इतने अलग-अलग प्रकार की वस्तुओं से 'अगली' आइटम कैसे प्राप्त करें?
उत्तर एक शक्तिशाली और सुरुचिपूर्ण डिजाइन पैटर्न में निहित है जिसे इटरेटर प्रोटोकॉल के रूप में जाना जाता है। यह प्रोटोकॉल वह आम भाषा है जो पायथन की सभी अनुक्रम जैसी वस्तुएं बोलती हैं। इस प्रोटोकॉल को समझकर और लागू करके, आप अपनी स्वयं की कस्टम ऑब्जेक्ट बना सकते हैं जो पायथन के इटेशन टूल के साथ पूरी तरह से संगत हैं, जिससे आपका कोड अधिक अभिव्यंजक, मेमोरी-कुशल और विशिष्ट रूप से 'पायथनिक' बन जाता है।
यह व्यापक मार्गदर्शिका आपको इटरेटर प्रोटोकॉल में गहराई से ले जाएगी। हम `__iter__` और `__next__` विधियों के पीछे के जादू को उजागर करेंगे, एक इटरेबल और एक इटरेटर के बीच महत्वपूर्ण अंतर को स्पष्ट करेंगे, और आपको अपनी स्वयं की कस्टम इटरेटर को स्क्रैच से बनाने में मार्गदर्शन करेंगे। चाहे आप पायथन के आंतरिक कामकाज की अपनी समझ को गहरा करने के लिए एक मध्यवर्ती डेवलपर हों या अधिक परिष्कृत एपीआई डिजाइन करने के उद्देश्य से एक विशेषज्ञ हों, इटरेटर प्रोटोकॉल में महारत हासिल करना आपकी यात्रा में एक महत्वपूर्ण कदम है।
'क्यों': इटेशन का महत्व और शक्ति
तकनीकी कार्यान्वयन में जाने से पहले, यह सराहना करना आवश्यक है कि इटरेटर प्रोटोकॉल इतना महत्वपूर्ण क्यों है। इसके लाभ केवल `for` लूप को सक्षम करने से कहीं आगे जाते हैं।
मेमोरी दक्षता और आलसी मूल्यांकन
कल्पना कीजिए कि आपको एक विशाल लॉग फ़ाइल को संसाधित करने की आवश्यकता है जो कई गीगाबाइट आकार की है। यदि आप पूरी फ़ाइल को मेमोरी में एक सूची में पढ़ते हैं, तो आप संभवतः अपनी सिस्टम के संसाधनों को समाप्त कर देंगे। इटरेटर आलसी मूल्यांकन नामक अवधारणा के माध्यम से इस समस्या को खूबसूरती से हल करते हैं।
एक इटरेटर एक ही बार में सभी डेटा लोड नहीं करता है। इसके बजाय, यह एक समय में एक आइटम उत्पन्न करता है या प्राप्त करता है, केवल तभी जब इसकी अनुरोध की जाती है। यह अनुक्रम में अपनी स्थिति को याद रखने के लिए एक आंतरिक स्थिति बनाए रखता है। इसका मतलब है कि आप बहुत कम, स्थिर मात्रा में मेमोरी के साथ (सिद्धांत रूप में) डेटा की असीम रूप से बड़ी स्ट्रीम को संसाधित कर सकते हैं। यही वह सिद्धांत है जो आपको अपने प्रोग्राम को क्रैश किए बिना एक विशाल फ़ाइल को लाइन-बाय-लाइन पढ़ने की अनुमति देता है।
स्वच्छ, पठनीय और सार्वभौमिक कोड
इटरेटर प्रोटोकॉल क्रमिक पहुंच के लिए एक सार्वभौमिक इंटरफ़ेस प्रदान करता है। क्योंकि सूची, टपल, शब्दकोश, स्ट्रिंग, फ़ाइल ऑब्जेक्ट और कई अन्य प्रकार सभी इस प्रोटोकॉल का पालन करते हैं, इसलिए आप उन सभी के साथ काम करने के लिए समान सिंटैक्स - `for` लूप - का उपयोग कर सकते हैं। यह एकरूपता पायथन की पठनीयता की आधारशिला है।
इस कोड पर विचार करें:
कोड:
my_list = [1, 2, 3]
for item in my_list:
print(item)
my_string = "abc"
for char in my_string:
print(char)
with open('my_file.txt', 'r') as f:
for line in f:
print(line)
`for` लूप को इस बात की परवाह नहीं है कि यह पूर्णांकों की सूची, वर्णों की एक स्ट्रिंग या फ़ाइल से पंक्तियों पर पुनरावृति कर रहा है। यह बस ऑब्जेक्ट से अपने इटरेटर के लिए पूछता है और फिर बार-बार इटरेटर से अपनी अगली आइटम के लिए पूछता है। यह अमूर्त अविश्वसनीय रूप से शक्तिशाली है।
इटरेटर प्रोटोकॉल का विखंडन
प्रोटोकॉल स्वयं आश्चर्यजनक रूप से सरल है, जिसे केवल दो विशेष विधियों द्वारा परिभाषित किया गया है, जिन्हें अक्सर "डंडर" (डबल अंडरस्कोर) विधियाँ कहा जाता है:
- `__iter__()`
- `__next__()`
इन्हें पूरी तरह से समझने के लिए, हमें पहले दो संबंधित लेकिन अलग अवधारणाओं के बीच अंतर को समझना होगा: एक इटरेबल और एक इटरेटर।
इटरेबल बनाम इटरेटर: एक महत्वपूर्ण अंतर
यह अक्सर नवागंतुकों के लिए भ्रम का एक बिंदु होता है, लेकिन अंतर महत्वपूर्ण है।
इटरेबल क्या है?
एक इटरेबल कोई भी ऑब्जेक्ट है जिसे लूप किया जा सकता है। यह एक ऑब्जेक्ट है जिसे आप एक इटरेटर प्राप्त करने के लिए अंतर्निहित `iter()` फ़ंक्शन में पास कर सकते हैं। तकनीकी रूप से, एक ऑब्जेक्ट को इटरेबल माना जाता है यदि यह `__iter__` विधि को लागू करता है। इसकी `__iter__` विधि का एकमात्र उद्देश्य एक इटरेटर ऑब्जेक्ट वापस करना है।
अंतर्निहित इटरेबल्स के उदाहरणों में शामिल हैं:
- सूची (`[1, 2, 3]`)
- टपल (`(1, 2, 3)`)
- स्ट्रिंग (`"hello"`)
- शब्दकोश (`{'a': 1, 'b': 2}` - कुंजियों पर पुनरावृति करता है)
- सेट (`{1, 2, 3}`)
- फ़ाइल ऑब्जेक्ट
आप एक इटरेबल को डेटा के कंटेनर या स्रोत के रूप में सोच सकते हैं। यह नहीं जानता कि आइटम को स्वयं कैसे तैयार किया जाए, लेकिन यह जानता है कि एक ऑब्जेक्ट कैसे बनाया जाए जो कर सकता है: इटरेटर।
इटरेटर क्या है?
एक इटरेटर वह ऑब्जेक्ट है जो वास्तव में इटेशन के दौरान मानों का उत्पादन करने का काम करता है। यह डेटा की एक स्ट्रीम का प्रतिनिधित्व करता है। एक इटरेटर को दो विधियों को लागू करना होगा:
- `__iter__()`: यह विधि इटरेटर ऑब्जेक्ट को स्वयं (`self`) वापस कर देनी चाहिए। यह आवश्यक है ताकि इटरेटर का उपयोग वहां भी किया जा सके जहां इटरेबल्स की उम्मीद की जाती है, उदाहरण के लिए, एक `for` लूप में।
- `__next__()`: यह विधि इटरेटर का इंजन है। यह अनुक्रम में अगली आइटम वापस करता है। जब वापस करने के लिए और आइटम नहीं होते हैं, तो इसे आवश्यक रूप से `StopIteration` अपवाद उठाना चाहिए। यह अपवाद कोई त्रुटि नहीं है; यह लूपिंग संरचना के लिए मानक संकेत है कि इटेशन पूरा हो गया है।
एक इटरेटर की मुख्य विशेषताएं हैं:
- यह स्थिति बनाए रखता है: एक इटरेटर अनुक्रम में अपनी वर्तमान स्थिति को याद रखता है।
- यह एक समय में एक मान उत्पन्न करता है: `__next__` विधि के माध्यम से।
- यह समाप्त होने योग्य है: एक बार जब एक इटरेटर पूरी तरह से उपभोग हो जाता है (अर्थात, इसने `StopIteration` उठाया है), तो यह खाली हो जाता है। आप इसे रीसेट या पुन: उपयोग नहीं कर सकते। फिर से इटरेट करने के लिए, आपको मूल इटरेबल पर वापस जाना होगा और उस पर `iter()` को फिर से कॉल करके एक नया इटरेटर प्राप्त करना होगा।
अपना पहला कस्टम इटरेटर बनाना: एक चरण-दर-चरण मार्गदर्शिका
सिद्धांत बहुत अच्छा है, लेकिन प्रोटोकॉल को समझने का सबसे अच्छा तरीका इसे स्वयं बनाना है। आइए एक सरल वर्ग बनाते हैं जो एक काउंटर के रूप में कार्य करता है, एक प्रारंभिक संख्या से एक सीमा तक पुनरावृति करता है।
उदाहरण 1: एक साधारण काउंटर वर्ग
हम `CountUpTo` नामक एक वर्ग बनाएंगे। जब आप इसका एक उदाहरण बनाते हैं, तो आप एक अधिकतम संख्या निर्दिष्ट करेंगे, और जब आप इस पर पुनरावृति करेंगे, तो यह 1 से लेकर उस अधिकतम तक की संख्याएँ उत्पन्न करेगा।
कोड:
class CountUpTo:
"""एक इटरेटर जो 1 से निर्दिष्ट अधिकतम संख्या तक गिनता है।"""
def __init__(self, max_num):
print("CountUpTo ऑब्जेक्ट को इनिशियलाइज़ किया जा रहा है...")
self.max_num = max_num
self.current = 0 # यह स्थिति को संग्रहीत करेगा
def __iter__(self):
print("__iter__ को कॉल किया गया, self वापस किया जा रहा है...")
# यह ऑब्जेक्ट अपना स्वयं का इटरेटर है, इसलिए हम self वापस करते हैं
return self
def __next__(self):
print("__next__ को कॉल किया गया...")
if self.current < self.max_num:
self.current += 1
return self.current
else:
# यह महत्वपूर्ण भाग है: संकेत है कि हम हो गए हैं।
print("StopIteration उठाया जा रहा है।")
raise StopIteration
# इसका उपयोग कैसे करें
print("काउंटर ऑब्जेक्ट बनाया जा रहा है...")
counter = CountUpTo(3)
print("\nफॉर लूप शुरू किया जा रहा है...")
for number in counter:
print(f"फॉर लूप को मिला: {number}")
कोड का टूटना और स्पष्टीकरण
आइए विश्लेषण करें कि `for` लूप चलने पर क्या होता है:
- इनिशियलाइजेशन: `counter = CountUpTo(3)` हमारे वर्ग का एक उदाहरण बनाता है। `__init__` विधि चलती है, `self.max_num` को 3 और `self.current` को 0 पर सेट करती है। हमारे ऑब्जेक्ट की स्थिति अब इनिशियलाइज़ हो गई है।
- लूप शुरू करना: जब `for number in counter:` लाइन तक पहुँच जाता है, तो पायथन आंतरिक रूप से `iter(counter)` को कॉल करता है।
- `__iter__` को कॉल किया जाता है: `iter(counter)` कॉल हमारी `counter.__iter__()` विधि को लागू करता है। जैसा कि आप हमारे कोड से देख सकते हैं, यह विधि बस एक संदेश प्रिंट करती है और `self` वापस करती है। यह `for` लूप को बताता है, "ऑब्जेक्ट जिस पर आपको `__next__` को कॉल करने की आवश्यकता है, वह मैं हूँ!"
- लूप शुरू होता है: अब `for` लूप तैयार है। प्रत्येक इटेशन में, यह प्राप्त इटरेटर ऑब्जेक्ट (जो हमारा `counter` ऑब्जेक्ट है) पर `next()` को कॉल करेगा।
- पहला `__next__` कॉल: `counter.__next__()` विधि को कॉल किया जाता है। `self.current` 0 है, जो `self.max_num` (3) से कम है। कोड `self.current` को 1 तक बढ़ाता है और इसे वापस करता है। `for` लूप इस मान को `number` चर को असाइन करता है, और लूप बॉडी (`print(...)`) निष्पादित होता है।
- दूसरा `__next__` कॉल: लूप जारी है। `__next__` को फिर से कॉल किया जाता है। `self.current` 1 है। इसे 2 तक बढ़ाया जाता है और वापस किया जाता है।
- तीसरा `__next__` कॉल: `__next__` को फिर से कॉल किया जाता है। `self.current` 2 है। इसे 3 तक बढ़ाया जाता है और वापस किया जाता है।
- अंतिम `__next__` कॉल: `__next__` को एक बार और कॉल किया जाता है। अब, `self.current` 3 है। शर्त `self.current < self.max_num` गलत है। `else` ब्लॉक निष्पादित होता है, और `StopIteration` उठाया जाता है।
- लूप समाप्त करना: `for` लूप को `StopIteration` अपवाद को पकड़ने के लिए डिज़ाइन किया गया है। जब ऐसा होता है, तो इसे पता चलता है कि इटेशन समाप्त हो गया है और शालीनता से समाप्त हो जाता है। प्रोग्राम लूप के बाद किसी भी कोड को निष्पादित करना जारी रखता है।
एक महत्वपूर्ण विवरण पर ध्यान दें: यदि आप उसी `counter` ऑब्जेक्ट पर फिर से `for` लूप चलाने का प्रयास करते हैं, तो यह काम नहीं करेगा। इटरेटर समाप्त हो गया है। `self.current` पहले से ही 3 है, इसलिए `__next__` को कोई भी बाद का कॉल तुरंत `StopIteration` उठाएगा। यह हमारे ऑब्जेक्ट के अपने इटरेटर होने का परिणाम है।
उन्नत इटरेटर अवधारणाएँ और वास्तविक दुनिया के अनुप्रयोग
सरल काउंटर सीखने का एक शानदार तरीका है, लेकिन इटरेटर प्रोटोकॉल की वास्तविक शक्ति तब चमकती है जब इसे अधिक जटिल, कस्टम डेटा संरचनाओं पर लागू किया जाता है।
इटरेबल और इटरेटर के संयोजन में समस्या
हमारे `CountUpTo` उदाहरण में, वर्ग इटरेबल और इटरेटर दोनों था। यह सरल है लेकिन इसका एक बड़ा नुकसान है: परिणामी इटरेटर समाप्त होने योग्य है। एक बार जब आप इस पर लूप करते हैं, तो यह हो जाता है।
कोड:
counter = CountUpTo(2)
print("पहला इटेशन:")
for num in counter: print(num) # ठीक काम करता है
print("\nदूसरा इटेशन:")
for num in counter: print(num) # कुछ भी नहीं प्रिंट करता है!
ऐसा इसलिए होता है क्योंकि स्थिति (`self.current`) ऑब्जेक्ट पर ही संग्रहीत होती है। पहले लूप के बाद, `self.current` 2 है, और कोई भी आगे का `__next__` कॉल केवल `StopIteration` उठाएगा। यह व्यवहार एक मानक पायथन सूची से अलग है, जिसे आप कई बार इटरेट कर सकते हैं।
एक अधिक मजबूत पैटर्न: इटरेबल को इटरेटर से अलग करना
पायथन के अंतर्निहित संग्रहों की तरह पुन: प्रयोज्य इटरेबल बनाने के लिए, सबसे अच्छा अभ्यास दो भूमिकाओं को अलग करना है। कंटेनर ऑब्जेक्ट इटरेबल होगा, और यह हर बार अपनी `__iter__` विधि को कॉल करने पर एक नया, ताज़ा इटरेटर ऑब्जेक्ट उत्पन्न करेगा।
आइए अपने उदाहरण को दो वर्गों में फिर से तैयार करें: `Sentence` (इटरेबल) और `SentenceIterator` (इटरेटर)।
कोड:
class SentenceIterator:
"""इटरेटर जो राज्य के लिए जिम्मेदार है और मूल्यों का उत्पादन करता है।"""
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
# एक इटरेटर को इटरेबल भी होना चाहिए, जो खुद को वापस करता है।
return self
class Sentence:
"""इटरेबल कंटेनर वर्ग।"""
def __init__(self, text):
# कंटेनर डेटा रखता है।
self.words = text.split()
def __iter__(self):
# हर बार जब __iter__ को कॉल किया जाता है, तो यह एक नया इटरेटर ऑब्जेक्ट बनाता है।
return SentenceIterator(self.words)
# इसका उपयोग कैसे करें
my_sentence = Sentence('This is a test')
print("पहला इटेशन:")
for word in my_sentence:
print(word)
print("\nदूसरा इटेशन:")
for word in my_sentence:
print(word)
अब, यह बिल्कुल एक सूची की तरह काम करता है! हर बार जब `for` लूप शुरू होता है, तो यह `my_sentence.__iter__()` को कॉल करता है, जो अपनी स्थिति (`self.index = 0`) के साथ एक बिल्कुल नया `SentenceIterator` उदाहरण बनाता है। यह एक ही `Sentence` ऑब्जेक्ट पर कई, स्वतंत्र इटेशन के लिए अनुमति देता है। यह पैटर्न कहीं अधिक मजबूत है और यही वह तरीका है जिससे पायथन के अपने संग्रह लागू किए जाते हैं।
उदाहरण: अनंत इटरेटर
इटरेटर को परिमित होने की आवश्यकता नहीं है। वे डेटा के अंतहीन अनुक्रम का प्रतिनिधित्व कर सकते हैं। यहीं पर उनका आलसी, एक-समय का स्वभाव एक बहुत बड़ा लाभ है। आइए फाइबोनैचि संख्याओं के अनंत अनुक्रम के लिए एक इटरेटर बनाते हैं।
कोड:
class FibonacciIterator:
"""फाइबोनैचि संख्याओं का एक अनंत अनुक्रम उत्पन्न करता है।"""
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
result = self.a
self.a, self.b = self.b, self.a + self.b
return result
# इसका उपयोग कैसे करें - चेतावनी: ब्रेक के बिना अनंत लूप!
fib_gen = FibonacciIterator()
for i, num in enumerate(fib_gen):
print(f"फाइबोनैचि({i}): {num}")
if i >= 10: # हमें एक रोक की स्थिति प्रदान करनी चाहिए
break
यह इटरेटर अपने आप `StopIteration` कभी नहीं उठाएगा। लूप को समाप्त करने के लिए एक शर्त (जैसे `break` स्टेटमेंट) प्रदान करना कॉलिंग कोड की जिम्मेदारी है। यह पैटर्न डेटा स्ट्रीमिंग, इवेंट लूप और संख्यात्मक सिमुलेशन में आम है।
पायथन इकोसिस्टम में इटरेटर प्रोटोकॉल
`__iter__` और `__next__` को समझने से आप पायथन में हर जगह उनके प्रभाव को देख सकते हैं। यह एकीकृत प्रोटोकॉल है जो पायथन की इतनी सारी सुविधाओं को एक साथ निर्बाध रूप से काम करता है।
`for` लूप *वास्तव में* कैसे काम करते हैं
हमने इस पर परोक्ष रूप से चर्चा की है, लेकिन आइए इसे स्पष्ट करें। जब पायथन इस पंक्ति का सामना करता है:
`for item in my_iterable:`
यह पर्दे के पीछे निम्नलिखित चरण करता है:
- यह एक इटरेटर प्राप्त करने के लिए `iter(my_iterable)` को कॉल करता है। यह बदले में, `my_iterable.__iter__()` को कॉल करता है। आइए वापस किए गए ऑब्जेक्ट को `iterator_obj` कहें।
- यह एक अनंत `while True` लूप में प्रवेश करता है।
- लूप के अंदर, यह `next(iterator_obj)` को कॉल करता है, जो बदले में `iterator_obj.__next__()` को कॉल करता है।
- यदि `__next__` एक मान वापस करता है, तो इसे `item` चर को असाइन किया जाता है, और `for` लूप ब्लॉक के अंदर का कोड निष्पादित होता है।
- यदि `__next__` एक `StopIteration` अपवाद उठाता है, तो `for` लूप इस अपवाद को पकड़ता है और अपने आंतरिक `while` लूप से बाहर निकल जाता है। इटेशन पूरा हो गया है।
समझ और जेनरेटर एक्सप्रेशन
सूची, सेट और शब्दकोश समझ सभी इटरेटर प्रोटोकॉल द्वारा संचालित हैं। जब आप लिखते हैं:
`squares = [x * x for x in range(10)]`
पायथन प्रभावी रूप से `range(10)` ऑब्जेक्ट पर एक इटेशन कर रहा है, प्रत्येक मान प्राप्त कर रहा है, और सूची बनाने के लिए अभिव्यक्ति `x * x` को निष्पादित कर रहा है। जेनरेटर एक्सप्रेशन के लिए भी यही सच है, जो आलसी इटेशन का और भी प्रत्यक्ष उपयोग हैं:
`lazy_squares = (x * x for x in range(1000000))`
यह मेमोरी में एक मिलियन-आइटम सूची नहीं बनाता है। यह एक इटरेटर (विशेष रूप से, एक जेनरेटर ऑब्जेक्ट) बनाता है जो जैसे ही आप इस पर इटरेट करते हैं, वर्गों को एक-एक करके गणना करेगा।
जेनरेटर: इटरेटर बनाने का सरल तरीका
हालांकि `__iter__` और `__next__` के साथ एक पूर्ण वर्ग बनाने से आपको अधिकतम नियंत्रण मिलता है, यह सरल मामलों के लिए वर्बोस हो सकता है। पायथन इटरेटर बनाने के लिए एक बहुत अधिक संक्षिप्त सिंटैक्स प्रदान करता है: जेनरेटर।
एक जेनरेटर एक फ़ंक्शन है जो `yield` कीवर्ड का उपयोग करता है। जब आप एक जेनरेटर फ़ंक्शन को कॉल करते हैं, तो यह कोड नहीं चलाता है। इसके बजाय, यह एक जेनरेटर ऑब्जेक्ट वापस करता है, जो एक पूर्ण इटरेटर है।
आइए अपने `CountUpTo` उदाहरण को एक जेनरेटर के रूप में फिर से लिखें:
कोड:
def count_up_to_generator(max_num):
"""एक जेनरेटर फ़ंक्शन जो 1 से max_num तक की संख्या उत्पन्न करता है।"""
print("जेनरेटर शुरू हो गया...")
current = 1
while current <= max_num:
yield current # यहां रुकता है और एक मान वापस भेजता है
current += 1
print("जेनरेटर समाप्त हो गया।")
# इसका उपयोग कैसे करें
counter_gen = count_up_to_generator(3)
for number in counter_gen:
print(f"फॉर लूप को मिला: {number}")
देखें कि यह कितना सरल है! यहां `yield` कीवर्ड जादू है। जब `yield` का सामना होता है, तो फ़ंक्शन की स्थिति स्थिर हो जाती है, मान कॉलर को भेज दिया जाता है, और फ़ंक्शन रुक जाता है। अगली बार जब जेनरेटर ऑब्जेक्ट पर `__next__` को कॉल किया जाता है, तो फ़ंक्शन निष्पादन को वहीं से फिर से शुरू करता है जहाँ उसने छोड़ा था, जब तक कि यह किसी अन्य `yield` को हिट नहीं करता या फ़ंक्शन समाप्त नहीं हो जाता। जब फ़ंक्शन समाप्त हो जाता है, तो आपके लिए स्वचालित रूप से एक `StopIteration` उठाया जाता है।
पर्दे के पीछे, पायथन ने स्वचालित रूप से `__iter__` और `__next__` विधियों के साथ एक ऑब्जेक्ट बनाया है। हालांकि जेनरेटर अक्सर अधिक व्यावहारिक विकल्प होते हैं, लेकिन अंतर्निहित प्रोटोकॉल को समझना डिबगिंग, जटिल सिस्टम डिजाइन करने और यह समझने के लिए आवश्यक है कि पायथन के मुख्य यांत्रिकी कैसे काम करते हैं।
सर्वोत्तम अभ्यास और सामान्य कमियाँ
इटरेटर प्रोटोकॉल को लागू करते समय, सामान्य त्रुटियों से बचने के लिए इन दिशानिर्देशों को ध्यान में रखें।
सर्वोत्तम अभ्यास
- इटरेबल और इटरेटर को अलग करें: किसी भी कंटेनर ऑब्जेक्ट के लिए जिसे कई ट्रैवर्सल का समर्थन करना चाहिए, हमेशा इटरेटर को एक अलग वर्ग में लागू करें। कंटेनर की `__iter__` विधि को हर बार इटरेटर वर्ग का एक नया उदाहरण वापस करना चाहिए।
- हमेशा `StopIteration` उठाएं: `__next__` विधि को अंत को इंगित करने के लिए मज़बूती से `StopIteration` उठाना चाहिए। इसे भूलने से अनंत लूप हो जाएंगे।
- इटरेटर को इटरेबल होना चाहिए: इटरेटर की `__iter__` विधि को हमेशा `self` वापस करना चाहिए। यह एक इटरेटर को कहीं भी उपयोग करने की अनुमति देता है जहां एक इटरेबल की उम्मीद की जाती है।
- सरलता के लिए जेनरेटर को प्राथमिकता दें: यदि आपका इटरेटर तर्क सीधा है और इसे एक फ़ंक्शन के रूप में व्यक्त किया जा सकता है, तो एक जेनरेटर लगभग हमेशा क्लीनर और अधिक पठनीय होता है। एक पूर्ण इटरेटर वर्ग का उपयोग करें जब आपको इटरेटर ऑब्जेक्ट के साथ अधिक जटिल स्थिति या विधियों को जोड़ने की आवश्यकता हो।
सामान्य कमियाँ
- समाप्त होने योग्य इटरेटर समस्या: जैसा कि चर्चा की गई है, इस बात से अवगत रहें कि जब कोई ऑब्जेक्ट अपना स्वयं का इटरेटर होता है, तो इसका उपयोग केवल एक बार किया जा सकता है। यदि आपको कई बार इटरेट करने की आवश्यकता है, तो आपको या तो एक नया उदाहरण बनाना होगा या अलग इटरेबल/इटरेटर पैटर्न का उपयोग करना होगा।
- स्थिति को भूलना: `__next__` विधि को इटरेटर की आंतरिक स्थिति को संशोधित करना होगा (उदाहरण के लिए, एक इंडेक्स बढ़ाना या एक पॉइंटर को आगे बढ़ाना)। यदि स्थिति अपडेट नहीं की जाती है, तो `__next__` एक ही मान को बार-बार वापस कर देगा, जिससे शायद एक अनंत लूप हो जाएगा।
- इटरेट करते समय एक संग्रह को संशोधित करना: इटरेट करते समय एक संग्रह को संशोधित करना (उदाहरण के लिए, `for` लूप के अंदर एक सूची से आइटम हटाना जो उस पर इटरेट कर रहा है) अप्रत्याशित व्यवहार कर सकता है, जैसे कि आइटम को छोड़ना या अप्रत्याशित त्रुटियों को उठाना। यदि आपको मूल को संशोधित करने की आवश्यकता है तो संग्रह की प्रतिलिपि पर इटरेट करना आम तौर पर सुरक्षित होता है।
निष्कर्ष
इटरेटर प्रोटोकॉल, अपनी सरल `__iter__` और `__next__` विधियों के साथ, पायथन में इटेशन का आधार है। यह भाषा के डिजाइन दर्शन का प्रमाण है: सरल, सुसंगत इंटरफेस का पक्ष लेना जो शक्तिशाली और जटिल व्यवहार को सक्षम करते हैं। क्रमिक डेटा एक्सेस के लिए एक सार्वभौमिक अनुबंध प्रदान करके, प्रोटोकॉल `for` लूप, समझ और अनगिनत अन्य टूल को किसी भी ऑब्जेक्ट के साथ निर्बाध रूप से काम करने की अनुमति देता है जो इसकी भाषा बोलने का विकल्प चुनता है।
इस प्रोटोकॉल में महारत हासिल करके, आपने अपनी स्वयं की अनुक्रम जैसी वस्तुओं को बनाने की क्षमता को अनलॉक कर दिया है जो पायथन पारिस्थितिकी तंत्र में प्रथम श्रेणी के नागरिक हैं। अब आप ऐसे वर्ग लिख सकते हैं जो आलसी डेटा को संसाधित करके अधिक मेमोरी-कुशल हैं, मानक पायथन सिंटैक्स के साथ सफाई से एकीकृत करके अधिक सहज हैं, और अंततः, अधिक शक्तिशाली हैं। अगली बार जब आप एक `for` लूप लिखें, तो सतह के ठीक नीचे हो रहे `__iter__` और `__next__` के सुरुचिपूर्ण नृत्य की सराहना करने के लिए एक क्षण निकालें।