course
Iterator ऐसे ऑब्जेक्ट होते हैं जिन पर iteration किया जा सकता है। ये Python प्रोग्रामिंग भाषा की एक सामान्य विशेषता हैं, जिन्हें looping और list comprehensions के लिए खूबसूरती से इस्तेमाल किया जाता है। कोई भी ऑब्जेक्ट जो एक iterator निकाल सकता है, उसे iterable कहा जाता है.
एक iterator बनाने में काफ़ी काम शामिल होता है। उदाहरण के लिए, हर iterator ऑब्जेक्ट के इम्प्लीमेंटेशन में __iter__() और __next__() मेथड होने चाहिए। उपरोक्त आवश्यकताओं के अलावा, इम्प्लीमेंटेशन में ऑब्जेक्ट की आंतरिक स्थिति को ट्रैक करने और जब कोई और मान वापस नहीं किया जा सकता, तो StopIteration exception उठाने का तरीका भी होना चाहिए। इन नियमों को iterator प्रोटोकॉल कहा जाता है.
अपना खुद का iterator इम्प्लीमेंट करना एक लंबी प्रक्रिया है और हर बार आवश्यक नहीं होती। एक सरल विकल्प generator ऑब्जेक्ट का उपयोग करना है। Generator एक विशेष प्रकार का फ़ंक्शन है, जो yield कीवर्ड का उपयोग करके एक iterator लौटाता है, जिसे एक बार में एक मान के साथ iterate किया जा सकता है.
कब iterator इम्प्लीमेंट करना है और कब generator का उपयोग करना है—यह पहचानने की क्षमता आपको एक बेहतर Python प्रोग्रामर बनाएगी। इस ट्यूटोरियल के शेष भाग में, हम इन दोनों ऑब्जेक्ट्स के बीच के अंतर पर ज़ोर देंगे, जिससे आप विभिन्न स्थितियों के लिए सबसे उपयुक्त विकल्प चुन सकें.
शब्दावली
|
शब्द |
परिभाषा |
|
Iterable |
एक Python ऑब्जेक्ट जिस पर लूप चलाया जा सकता है या जिसे किसी लूप में iterate किया जा सकता है। Iterables के उदाहरण: lists, sets, tuples, dictionaries, strings, आदि. |
|
Iterator |
Iterator वह ऑब्जेक्ट है जिस पर iteration किया जा सकता है। इसलिए, iterators में गिने-चुने मान होते हैं. |
|
Generator |
एक विशेष प्रकार का फ़ंक्शन जो एकल मान वापस नहीं करता: यह मानों के अनुक्रम वाला एक iterator ऑब्जेक्ट लौटाता है। |
|
Lazy Evaluation |
एक ऐसी evaluation रणनीति जिसमें कुछ ऑब्जेक्ट तभी बनाए जाते हैं जब उनकी आवश्यकता होती है। इसी कारण कुछ डेवलपर समुदायों में lazy evaluation को “call-by-need” भी कहा जाता है। |
|
Iterator Protocol |
नियमों का वह सेट जिसे Python में एक iterator परिभाषित करने के लिए पालन करना आवश्यक है. |
|
next() |
एक built-in फ़ंक्शन जो किसी iterator में अगली वस्तु लौटाने के लिए उपयोग होता है. |
|
iter() |
एक built-in फ़ंक्शन जो किसी iterable को iterator में बदलने के लिए उपयोग होता है. |
|
yield() |
एक Python कीवर्ड जो |
Python Iterators और Iterables
Iterables ऐसे ऑब्जेक्ट होते हैं जो अपने सदस्य एक-एक करके वापस कर सकते हैं—यानी उन पर iteration किया जा सकता है। लोकप्रिय built-in Python डेटा संरचनाएँ जैसे lists, tuples, और sets iterables हैं। अन्य संरचनाएँ जैसे strings और dictionaries भी iterable मानी जाती हैं: एक string अपने अक्षरों का iteration करा सकती है, और dictionary की keys पर भी iterate किया जा सकता है। एक सामान्य नियम के रूप में, किसी भी ऑब्जेक्ट को जिसे for-loop में iterate किया जा सकता है, iterable मानें.
उदाहरणों के साथ Python iterables की पड़ताल
इन परिभाषाओं के आधार पर, हम निष्कर्ष निकाल सकते हैं कि सभी iterators भी iterable होते हैं। हालाँकि, हर iterable आवश्यक रूप से iterator नहीं होता। कोई iterable तभी iterator बनाता है जब उस पर iteration शुरू किया जाए।
इस फ़ंक्शनलिटी को दिखाने के लिए, हम एक list बनाएँगे, जो एक iterable है, और उस list पर built-in फ़ंक्शन iter() कॉल करके एक iterator बनाएँगे.
list_instance = [1, 2, 3, 4]
print(iter(list_instance))
"""
<list_iterator object at 0x7fd946309e90>
"""
यद्यपि list अपने आप में iterator नहीं है, iter() फ़ंक्शन कॉल करने पर यह एक iterator में बदल जाती है और iterator ऑब्जेक्ट लौटाती है।
यह दिखाने के लिए कि हर iterable iterator नहीं होता, हम वही list ऑब्जेक्ट बनाएँगे और next() फ़ंक्शन कॉल करने का प्रयास करेंगे, जो किसी iterator में अगली वस्तु वापस करने के लिए उपयोग होता है.
list_instance = [1, 2, 3, 4]
print(next(list_instance))
"""
--------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-0cb076ed2d65> in <module>()
3 print(iter(list_instance))
4
----> 5 print(next(list_instance))
TypeError: 'list' object is not an iterator
"""
ऊपर के कोड में, आप देख सकते हैं कि list पर next() कॉल करने की कोशिश करने पर TypeError उठा—Python में Exception और Error Handling के बारे में और जानें। यह व्यवहार इसलिए हुआ क्योंकि list ऑब्जेक्ट iterable है, iterator नहीं.
उदाहरणों के साथ Python iterators की पड़ताल
इसलिए, यदि लक्ष्य किसी list पर iterate करना है, तो पहले एक iterator ऑब्जेक्ट बनाना होगा। तभी हम list के मानों के माध्यम से iteration को नियंत्रित कर सकते हैं।
# instantiate a list object
list_instance = [1, 2, 3, 4]
# convert the list to an iterator
iterator = iter(list_instance)
# return items one at a time
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
"""
1
2
3
4
"""
जब भी आप किसी iterable ऑब्जेक्ट के माध्यम से लूप करने का प्रयास करते हैं, Python स्वतः एक iterator ऑब्जेक्ट बना देता है.
# instantiate a list object
list_instance = [1, 2, 3, 4]
# loop through the list
for item in list_instance:
print(item)
"""
1
2
3
4
"""
जब StopIteration exception पकड़ा जाता है, तो लूप समाप्त हो जाता है।
Iterator से प्राप्त मान केवल बाएँ से दाएँ क्रम में ही निकाले जा सकते हैं। Python में कोई previous() फ़ंक्शन नहीं है जो डेवलपर्स को iterator में पीछे जाने दे.
Iterators की lazy प्रकृति
एक ही iterable ऑब्जेक्ट से कई iterators परिभाषित करना संभव है। प्रत्येक iterator अपनी प्रगति की अलग स्थिति बनाए रखेगा। इसलिए, किसी iterable ऑब्जेक्ट के कई iterator instance बनाकर, एक instance को अंत तक iterate करना संभव है जबकि दूसरा instance शुरुआत में ही रहता है।
list_instance = [1, 2, 3, 4]
iterator_a = iter(list_instance)
iterator_b = iter(list_instance)
print(f"A: {next(iterator_a)}")
print(f"A: {next(iterator_a)}")
print(f"A: {next(iterator_a)}")
print(f"A: {next(iterator_a)}")
print(f"B: {next(iterator_b)}")
"""
A: 1
A: 2
A: 3
A: 4
B: 1
"""
ध्यान दें कि iterator_b श्रृंखला का पहला तत्व प्रिंट करता है।
इस प्रकार, हम कह सकते हैं कि iterators की प्रकृति lazy होती है: जब कोई iterator बनाया जाता है, तो तत्व तब तक उपलब्ध नहीं कराए जाते जब तक उनकी माँग न की जाए। दूसरे शब्दों में, हमारी list instance के तत्व तभी लौटाए जाएँगे जब हम स्पष्ट रूप से next(iter(list_instance)) से उन्हें माँगेंगे.
हालाँकि, सभी मानों को एक साथ भी निकाला जा सकता है—iterator ऑब्जेक्ट पर किसी built-in iterable डेटा-स्ट्रक्चर कंटेनर (जैसे list(), set(), tuple()) को कॉल करके—जो iterator को एक साथ उसके सभी तत्व जेनरेट करने के लिए मजबूर करता है।
# instantiate iterable
list_instance = [1, 2, 3, 4]
# produce an iterator from an iterable
iterator = iter(list_instance)
print(list(iterator))
"""
[1, 2, 3, 4]
"""
यह बड़े iterators के लिए अनुशंसित नहीं है क्योंकि यह हर तत्व को एक साथ जेनरेट और मेमोरी में होल्ड करने के लिए मजबूर करता है, जिससे lazy evaluation का उद्देश्य विफल हो जाता है।
जब कोई dataset मेमोरी में आराम से फिट होने के लिए बहुत बड़ा हो, या जब आप बिना पूरा iterator क्लास लिखे lazy iteration चाहते हों, तो आम तौर पर generator बेहतर विकल्प होता है।
Python Generators
Iterator इम्प्लीमेंट करने का सबसे तेज़ विकल्प generator का उपयोग करना है। हालाँकि generators साधारण Python फ़ंक्शंस जैसे दिखते हैं, वे अलग होते हैं। शुरुआत के लिए, एक generator ऑब्जेक्ट वस्तुओं को वापस नहीं करता। इसके बजाय, यह तुरंत मान जेनरेट करने के लिए yield कीवर्ड का उपयोग करता है। इस प्रकार, हम कह सकते हैं कि generator एक विशेष प्रकार का फ़ंक्शन है जो lazy evaluation का लाभ उठाता है।
Generators अपनी सामग्री को मेमोरी में स्टोर नहीं करते, जैसा कि आप किसी सामान्य iterable से अपेक्षा करते हैं। उदाहरण के लिए, यदि लक्ष्य किसी धनात्मक पूर्णांक के सभी गुणनखंड ढूँढ़ना हो, तो हम आम तौर पर एक पारंपरिक फ़ंक्शन (इस ट्यूटोरियल में Python Functions के बारे में और जानें) इस प्रकार इम्प्लीमेंट करेंगे:
def factors(n):
factor_list = []
for val in range(1, n+1):
if n % val == 0:
factor_list.append(val)
return factor_list
print(factors(20))
"""
[1, 2, 4, 5, 10, 20]
"""
ऊपर का कोड सभी गुणनखंडों की पूरी सूची लौटाता है। हालाँकि, ध्यान दें कि जब पारंपरिक Python फ़ंक्शन की जगह generator का उपयोग किया जाता है, तो क्या अंतर आता है:
def factors(n):
for val in range(1, n+1):
if n % val == 0:
yield val
print(factors(20))
"""
<generator object factors at 0x7fd938271350>
"""
क्योंकि हमने yield कीवर्ड का उपयोग किया, return नहीं, इसलिए रन के बाद फ़ंक्शन से बाहर नहीं निकला गया। मूल रूप से, हमने Python से कहा कि वह एक पारंपरिक फ़ंक्शन की बजाय एक generator ऑब्जेक्ट बनाए, जो generator ऑब्जेक्ट की स्थिति को ट्रैक करने में सक्षम बनाता है.
फलस्वरूप, lazy iterator पर next() फ़ंक्शन कॉल करके श्रृंखला के तत्वों को एक-एक करके दिखाना संभव है.
def factors(n):
for val in range(1, n+1):
if n % val == 0:
yield val
factors_of_20 = factors(20)
print(next(factors_of_20))
"""
1
"""
Generator बनाने का एक और तरीका generator comprehension है। Generator expressions का syntax list comprehension जैसा ही होता है, सिवाय इसके कि इसमें चौकोर ब्रैकेट की जगह गोल ब्रैकेट का उपयोग होता है।
factor_gen = (val for val in range(1, 21) if 20 % val == 0)
print(list(factor_gen))
"""
[1, 2, 4, 5, 10, 20]
"""
Python के yield कीवर्ड की पड़ताल
yield कीवर्ड किसी generator फ़ंक्शन के नियंत्रण प्रवाह को नियंत्रित करता है। return उपयोग करने पर फ़ंक्शन से बाहर निकलने के बजाय, yield फ़ंक्शन से लौटता है लेकिन उसके स्थानीय वेरिएबल्स की स्थिति याद रखता है।
yield कॉल से लौटे generator को किसी वेरिएबल में असाइन किया जा सकता है और next() फ़ंक्शन से iterate किया जा सकता है—यह फ़ंक्शन को तब तक निष्पादित करेगा जब तक उसे पहला yield कीवर्ड न मिल जाए। एक बार yield आने पर, फ़ंक्शन का execution निलंबित हो जाता है। जब ऐसा होता है, तो फ़ंक्शन की स्थिति सहेज ली जाती है। इस प्रकार, हम अपनी सुविधा के अनुसार फ़ंक्शन का execution फिर से शुरू कर सकते हैं.
फ़ंक्शन yield कॉल से आगे जारी रहेगा। उदाहरण के लिए:
def yield_multiple_statements():
yield "This is the first statement"
yield "This is the second statement"
yield "This is the third statement"
yield "This is the last statement. Don't call next again!"
example = yield_multiple_statements()
print(next(example))
print(next(example))
print(next(example))
print(next(example))
print(next(example))
"""
This is the first statement
This is the second statement
This is the third statement
This is the last statement. Don't call next again or else!
--------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-25-4aaf9c871f91> in <module>()
11 print(next(example))
12 print(next(example))
---> 13 print(next(example))
StopIteration:
"""
ऊपर के कोड में, हमारे generator में चार yield कॉल हैं, लेकिन हम उस पर पाँच बार next कॉल करने का प्रयास करते हैं, जिससे StopIteration exception उठता है। यह इसलिए हुआ क्योंकि हमारा generator अनंत श्रृंखला नहीं है, इसलिए अपेक्षा से अधिक बार कॉल करने पर generator समाप्त हो गया।
समापन
सारांश के लिए, iterators वे ऑब्जेक्ट हैं जिन पर iteration किया जा सकता है, और generators वे विशेष फ़ंक्शन हैं जो lazy evaluation का लाभ उठाते हैं। अपना iterator इम्प्लीमेंट करने का अर्थ है कि आपको __iter__() और __next__() मेथड बनाने होंगे, जबकि generator को Python फ़ंक्शन या comprehension में yield कीवर्ड से इम्प्लीमेंट किया जा सकता है.
जब आपको किसी ऐसे ऑब्जेक्ट की आवश्यकता हो जो जटिल state-maintaining व्यवहार रखता हो या आप __next__(), __iter__(), और __init__() के अलावा अन्य मेथड्स भी एक्सपोज़ करना चाहें, तब आप custom iterator को generator पर वरीयता दे सकते हैं। दूसरी ओर, बड़े डेटा सेट्स के साथ काम करते समय, क्योंकि generators अपनी सामग्री मेमोरी में स्टोर नहीं करते, या जब iterator को इम्प्लीमेंट करना आवश्यक न हो, तो generator अधिक उपयुक्त हो सकता है.
FAQS
Python में iterator और generator में क्या अंतर है?
Iterator वह कोई भी ऑब्जेक्ट है जो __iter__() और __next__() इम्प्लीमेंट करता है। Generator एक सरल तरीका है iterator बनाने का, जिसमें फ़ंक्शन में yield कीवर्ड का उपयोग होता है। सभी generators iterators होते हैं, लेकिन सभी iterators generators नहीं होते।
मुझे Python में list की बजाय generator कब उपयोग करना चाहिए?
बड़े या अनंत अनुक्रमों के लिए, या जब मेमोरी दक्षता मायने रखती है, तो generator का उपयोग करें। Lists हर तत्व को एक साथ मेमोरी में रखती हैं, जबकि generators एक समय में एक मान पैदा करते हैं। छोटे datasets जिन्हें आप बार-बार उपयोग करेंगे, उनके लिए list आमतौर पर ठीक रहती है।
Python में yield कीवर्ड क्या करता है?
yield कीवर्ड किसी फ़ंक्शन को generator में बदल देता है। yield वापस लौटकर फ़ंक्शन को रोकता है, एक मान देता है, और उसकी स्थिति याद रखता है ताकि अगली कॉल पर वहीं से निष्पादन फिर शुरू हो सके।
Python में generator कैसे बनाया जाता है?
या तो ऐसा फ़ंक्शन लिखें जो return की जगह yield का उपयोग करे, या generator expression का प्रयोग करें — list comprehension जैसा ही syntax, लेकिन गोल ब्रैकेट के साथ, जैसे (x * 2 for x in range(10))।
क्या Python में generators, iterators से तेज़ होते हैं?
कच्ची गति में नहीं, लेकिन वे अधिक मेमोरी-कुशल होते हैं क्योंकि वे माँग पर मान पैदा करते हैं। बड़े datasets के लिए इसका मतलब अक्सर बेहतर समग्र प्रदर्शन होता है; छोटे datasets में अंतर नगण्य होता है।