Kodokon kodokon.com

इटरेटर और जेनरेटर: yield और पाइपलाइन

इटरेशन प्रोटोकॉल और जेनरेटर में महारत हासिल करें, ताकि डेटा स्ट्रीम को स्थिर मेमोरी में, आलसी ढंग से प्रोसेस किया जा सके।

10 मिनट · 3 प्रश्न

इस पाठ को Kodokon में खोलें

Python का for एक प्रोटोकॉल से ज़्यादा कुछ नहीं है: iter(obj) एक इटरेटर माँगता है, फिर StopIteration अपवाद आने तक next() कॉल होता रहता है। इटरेबल (वह लिस्ट, जो हर बार एक ताज़ा इटरेटर सौंप सकती है) और इटरेटर (वह कर्सर, जिसे केवल एक बार पार किया जा सकता है) में अंतर करना न भूलें। इस प्रोटोकॉल को समझने का मतलब है यह समझना कि कुछ ऑब्जेक्ट "खप क्यों जाते हैं।"

PYTHON
numbers = [10, 20, 30]
iterator = iter(numbers)
print(next(iterator))
print(next(iterator))
print(next(iterator))
# next(iterator) would raise StopIteration
एक for लूप जो करता है, वही हाथ से

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

PYTHON
def squares(limit):
    n = 0
    while n < limit:
        yield n * n
        n += 1

gen = squares(4)
print(next(gen))  # 0
print(list(gen))  # [1, 4, 9]
yield दो वैल्यू के बीच फ़ंक्शन को निलंबित कर देता है

प्रोडक्शन में मुख्य लाभ: पाइपलाइन। हर चरण पिछले चरण की स्ट्रीम को खपाता है और आलसी ढंग से अपनी खुद की स्ट्रीम उत्पन्न करता है; अंतिम खपत (list(), sum(), कोई for) से पहले कुछ भी नहीं चलता। ध्यान में रखने लायक एक समझौता: किसी लिस्ट को कई बार पार किया जा सकता है और स्लाइस किया जा सकता है, जेनरेटर को नहीं; दूसरी ओर, जेनरेटर 10 GB की फ़ाइल को RAM भरे बिना प्रोसेस कर लेता है। जेनरेटर एक्सप्रेशन - (f(x) for x in xs if cond) - बिना कोई फ़ंक्शन परिभाषित किए सरल चरणों को संभाल लेते हैं।

PYTHON
lines = ["  12 ", "x", " 7", "", "30"]

def clean(rows):
    for row in rows:
        row = row.strip()
        if row.isdigit():
            yield int(row)

def keep_from(values, floor):
    return (v for v in values if v >= floor)

pipeline = keep_from(clean(lines), 10)
print(list(pipeline))  # [12, 30]
एक आलसी पाइपलाइन: list() से पहले कुछ भी नहीं चलता

ज्ञान जांच

सुनिश्चित करें कि आपको इस पाठ के मुख्य बिंदु याद हैं।

  1. जब आप किसी ऐसे फ़ंक्शन को कॉल करते हैं जिसके मुख्य भाग में yield है, तो आपको क्या मिलता है?
    • पहले yield का परिणाम
    • एक जेनरेटर, मुख्य भाग के निष्पादन के बिना
    • उत्पन्न की गई सभी वैल्यू की एक लिस्ट
  2. मध्यवर्ती लिस्ट की तुलना में जेनरेटर की पाइपलाइन का निर्णायक लाभ क्या है?
    • परिणाम अपने आप क्रमबद्ध हो जाते हैं
    • मेमोरी स्थिर रहती है: वैल्यू माँग पर उत्पन्न होती हैं
    • प्रोसेसिंग कई कोर पर समानांतर हो जाती है
  3. पहले list(gen) के बाद, उसी जेनरेटर पर दूसरा list(gen) क्या लौटाता है?
    • पहली कॉल जैसी ही लिस्ट
    • एक खाली लिस्ट
    • एक अपवाद प्रसारित होता है