जे

जेनरेटर में Python

1 अभ्यास

जेनरेटर के बारे में

एक generator एक फंक्शन या एक्सप्रेशन है जो एक खास तरह का इटरेटर लौटाता है, जिसे जनरेटर इटरेटर कहते हैं। Generator-iterators लेज़ी होते हैं: वे अपनी values मेमोरी में जमा नहीं करते, बल्कि ज़रूरत पड़ने पर ही अपनी वैल्यू बनाते हैं।

एक जनरेटर फंक्शन दिखने में किसी भी दूसरे फंक्शन जैसा होता है, लेकिन उसमें एक या एक से अधिक yield एक्सप्रेशन होते हैं। हर yield कोड का निष्पादन रोक देता है और मौजूदा निष्पादन स्थिति (सभी लोकल वेरिएबलो और try स्टेटमेंट सहित) सहेज लेता है। जब जनरेटर दोबारा चलता है, तो वह उसी स्थिति से आगे बढ़ता है जहाँ वह रुका था। साधारण फंक्शन ऐसा नहीं करते, वे हर कॉल पर फिर से शुरू हो जाते हैं।

जनरेटर बनाना

जनरेटर बनाना बहुत हद तक दूसरे लूप वाले या रिकर्सिव फंक्शन जैसा ही होता है, लेकिन इसमें एक yield एक्सप्रेशन चाहिए, जिसे हम थोड़ी देर बाद गहराई से देखेंगे।

इसका एक उदाहरण वह फंक्शन है जो संख्याओं के दिए गए ऐरे से उनके वर्ग लौटाता है।
अभी जिस तरह यह लिखा गया है, उसमें कोई भी वैल्यू लौटाने से पहले सारे इनपुट पर काम करना ज़रूरी है:

>>> def squares(list_of_numbers):
...     squares = []
...     for number in list_of_numbers:
...         squares.append(number ** 2)
...     return squares

आप उस फंक्शन को इस तरह जनरेटर में बदल सकते हैं:

>>> def squares_generator(list_of_numbers):
...     for number in list_of_numbers:
...         yield number ** 2

इसके पीछे की सोच यह है कि जनरेटर का इस्तेमाल आप तब करते हैं जब आपको सारी वैल्यू एक साथ नहीं चाहिए।

इससे मेमोरी और प्रोसेसिंग शक्ति बचती है, क्योंकि सिर्फ़ उसी वैल्यू की गणना होती है जिस पर आप इस समय काम कर रहे हैं।

जनरेटर का इस्तेमाल

जनरेटर (तकनीकी रूप से generator-iterators, नीचे दिया नोट देखिए।) एक तरह के iterator होते हैं और Python में हर उस जगह इस्तेमाल किए जा सकते हैं जहाँ किसी iterator या iterable की उम्मीद होती है। इसमें वे फंक्शन या ऑब्जेक्ट भी शामिल हैं जिन्हें आर्गुमेंट के तौर पर कोई iterable/iterator चाहिए। और गहराई में जाने के लिए How to Make an Iterator in Python देखिए।

Note

जनरेटर-इटरेटर, इटरेटरो का एक खास उपसमूह हैं। Iterators वह तंत्र या प्रोटोकॉल हैं जिससे इटरेबल पर लूप चलाया जा सकता है। जनरेटर-इटरेटर और Python के आम iterables से मिलने वाले इटरेटर लगभग एक जैसा व्यवहार करते हैं, लेकिन कुछ ज़रूरी अंतर ध्यान देने योग्य हैं:

  • ये लेज़ी तरीके से मूल्यांकित होते हैं; इटरेशन एकतरफा होता है और किसी पिछली वैल्यू पर "वापस जाने" का कोई तरीका नहीं होता।
  • लौटाई गई वैल्यू पर इटरेशन करने से ये खर्च हो जाते हैं; इन्हें रीसेट नहीं किया जा सकता और न ही मेमोरी में सहेजा जा सकता है।
  • इन्हें क्रम में नहीं लगाया जा सकता और न ही उलटा चलाया जा सकता है।
  • ये सीक्वेंस टाइप नहीं होते, और इनमें indexes नहीं होते। आप जोड़ या घटाव से किसी पिछली या आगे की वैल्यू का संदर्भ नहीं ले सकते, और न ही ब्रैकेट ([]) नोटेशन या स्लाइसिंग का इस्तेमाल कर सकते हैं।
  • इनकी कोई लंबाई नहीं होती, इसलिए इन्हें len() फंक्शन के साथ इस्तेमाल नहीं किया जा सकता।
  • ये सीमित या असीमित हो सकते हैं। किसी असीमित generator-iterator से सारी वैल्यू जमा करते समय सावधान रहिए!

squares_generator() जनरेटर का इस्तेमाल करने के लिए:

>>> squared_numbers = squares_generator([1, 2, 3, 4])

>>> for square in squared_numbers:
...     print(square)
...
1
4
9
16

जनरेटर की वैल्यू को next() फंक्शन से भी निकाला या पढ़ा जा सकता है। next() एक जनरेटर ऑब्जेक्ट के __next__() मेथड को कॉल करता है। इससे जनरेटर का कोड उसके yield एक्सप्रेशन तक "आगे बढ़ता" है यानी मूल्यांकित होता है। फिर वही एक्सप्रेशन वैल्यू को "yield" करता है यानी लौटा देता है।

>>> squared_numbers = squares_generator([1, 2])

>>> next(squared_numbers)
1
>>> next(squared_numbers)
4

जब कोई generator पूरी तरह खर्च हो जाता है और उसके पास लौटाने के लिए कोई वैल्यू नहीं बचती, तो वह StopIteration एरर देता है।

>>> next(squared_numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

इटरेबल और जनरेटर में अंतर

जनरेटर, इटरेटरो का एक खास उपसमूह हैं। Iterators वह तंत्र या प्रोटोकॉल हैं जिससे इटरेबल पर लूप चलाया जा सकता है। जनरेटर और Python के आम iterables से मिलने वाले इटरेटर लगभग एक जैसा व्यवहार करते हैं, लेकिन कुछ ज़रूरी अंतर ध्यान देने योग्य हैं:

  • जनरेटर एकतरफा होते हैं; किसी पिछली वैल्यू पर "वापस जाने" का कोई तरीका नहीं होता।

  • जनरेटर पर इटरेशन करने से लौटाई गई वैल्यू खर्च हो जाती हैं; कोई रीसेट नहीं होता।

  • जनरेटर (चूँकि इनका मूल्यांकन लेज़ी तरीके से होता है) को क्रम में नहीं लगाया जा सकता और न ही उलटा चलाया जा सकता है।

  • जनरेटर में indexes नहीं होते, इसलिए आप जोड़ या घटाव से किसी पिछली या आगे की वैल्यू का संदर्भ नहीं ले सकते।

  • जनरेटर को len() फंक्शन के साथ इस्तेमाल नहीं किया जा सकता।

  • जनरेटर सीमित या असीमित हो सकते हैं, किसी असीमित जनरेटर से सारी वैल्यू जमा करते समय सावधान रहिए।

yield एक्सप्रेशन

yield एक्सप्रेशन, return एक्सप्रेशन से बहुत मिलता-जुलता है।

return एक्सप्रेशन के उलट, yield कॉलर को एक खास जगह पर वैल्यू सौंप देता है, और आगे की कोई भी वैल्यू माँगे जाने तक उसका मूल्यांकन या लौटाना रोक देता है।

जब yield का मूल्यांकन होता है, तो यह उस फंक्शन का निष्पादन रोक देता है जिसके अंदर वह लिखा है, और उसी समय फंक्शन की जो भी वैल्यू हैं, उन्हें लौटा देता है।

इसके बाद वह फंक्शन स्कोप में बना रहता है, और जब __next__() कॉल किया जाता है, तो निष्पादन तब तक आगे बढ़ता रहता है जब तक दोबारा yield न मिल जाए।

Note

फंक्शन के बाहर yield एक्सप्रेशन इस्तेमाल करना मना है।

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1

जनरेटर क्यों?

जनरेटर कई जगहों पर काम आते हैं।

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

जनरेटर तब भी बहुत काम आते हैं जब कोई प्रक्रिया या गणना जटिल, महँगी या असीमित हो:

>>> def infinite_sequence():
...     current_number = 0
...     while True:
...         yield current_number
...         current_number += 1

अब जब भी infinite_sequence ऑब्जेक्ट पर __next__() कॉल किया जाता है, तो यह पिछली संख्या + 1 लौटाता है।

GitHub के ज़रिए संपादित करें यह लिंक नई विंडो या टैब में खुलता है।

जेनरेटर सीखिए

अभ्यास लॉक है

जेनरेटर पर अभ्यास करने के लिए 2 और अभ्यास अनलॉक कीजिए