एक बेसिक पायथन इटरेटर का निर्माण करें


568

अजगर में एक पुनरावृत्त समारोह (या पुनरावृत्त वस्तु) कैसे होगा?

जवाबों:


649

अजगर में इटरेटर ऑब्जेक्ट्स इटरेटर प्रोटोकॉल के अनुरूप होते हैं, जिसका मूल अर्थ है कि वे दो तरीके प्रदान करते हैं: __iter__() और __next__()

  • __iter__इटरेटर ऑब्जेक्ट और परोक्ष छोरों के शुरू में कहा जाता है।

  • __next__()विधि अगले मान देता है और परोक्ष प्रत्येक पाश वेतन वृद्धि पर कहा जाता है। जब वापसी करने के लिए अधिक मूल्य नहीं होते हैं, तो इसे रोकना बंद कर दिया जाता है।

यहाँ एक काउंटर का सरल उदाहरण दिया गया है:

class Counter:
    def __init__(self, low, high):
        self.current = low - 1
        self.high = high

    def __iter__(self):
        return self

    def __next__(self): # Python 2: def next(self)
        self.current += 1
        if self.current < self.high:
            return self.current
        raise StopIteration


for c in Counter(3, 9):
    print(c)

यह प्रिंट करेगा:

3
4
5
6
7
8

जनरेटर का उपयोग करके लिखना आसान है, जैसा कि पिछले उत्तर में कवर किया गया है:

def counter(low, high):
    current = low
    while current < high:
        yield current
        current += 1

for c in counter(3, 9):
    print(c)

मुद्रित आउटपुट समान होगा। हुड के तहत, जनरेटर ऑब्जेक्ट पुनरावृत्त प्रोटोकॉल का समर्थन करता है और क्लास काउंटर के समान कुछ करता है।

डेविड मर्टज़ का लेख, Iterators और Simple Generators , एक बहुत अच्छा परिचय है।


4
यह ज्यादातर एक अच्छा जवाब है, लेकिन यह तथ्य यह है कि यह स्व वापस आता है, थोड़ा उप-इष्टतम है। उदाहरण के लिए, यदि आप लूप के लिए एक नेस्टेड नेस्ट में एक ही काउंटर ऑब्जेक्ट का उपयोग करते हैं, तो आपको संभवतः वह व्यवहार नहीं मिलेगा जो आपके लिए था।
केसी रोडर्मर

22
नहीं, पुनरावृत्तियाँ स्वयं वापस आएँगी। Iterables पुनरावृत्तियों को लौटाता है, लेकिन पुनरावृत्तियाँ लागू नहीं होनी चाहिए __next__counterएक पुनरावृत्ति है, लेकिन यह एक अनुक्रम नहीं है। यह अपने मूल्यों को संग्रहीत नहीं करता है। उदाहरण के लिए, आपको दोहरे-नेस्टेड-लूप में काउंटर का उपयोग नहीं करना चाहिए।
लीव्ज

4
काउंटर उदाहरण में, self.current को __iter__(इसके अलावा __init__) में असाइन किया जाना चाहिए । अन्यथा, ऑब्जेक्ट केवल एक बार पुनरावृत्त हो सकता है। उदाहरण के लिए, यदि आप कहते हैं ctr = Counters(3, 8), तो आप for c in ctrएक से अधिक बार उपयोग नहीं कर सकते ।
कर्ट

7
@ कर्ट: बिल्कुल नहीं। Counterएक पुनरावृत्ति है, और पुनरावृत्तियों केवल एक बार पुनरावृत्त होना चाहिए। यदि आप रीसेट करते self.currentहैं __iter__, तो एक नेस्टेड लूप Counterपूरी तरह से टूट जाएगा, और पुनरावृत्तियों के सभी प्रकार के व्यवहार (कि iterउन पर कॉल करना बेकार है) का उल्लंघन किया जाता है। यदि आप ctrएक से अधिक बार पुनरावृति करने में सक्षम होना चाहते हैं , तो इसे एक गैर- पुनरावृत्त पुनरावृत्त होने की आवश्यकता है, जहां यह हर बार __iter__लागू होने पर एक नया पुनरावृत्ति देता है। मिक्स एंड मैच करने की कोशिश (एक इटरेटर जो कि जब __iter__आह्वान किया जाता है तब इसे रीसेट किया जाता है) प्रोटोकॉल का उल्लंघन करता है।
शैडो रेंजर

2
उदाहरण के लिए, यदि Counterएक गैर-पुनरावृत्त पुनरावृत्ति होना था , तो आप __next__/ nextपूरी तरह से परिभाषा को हटा देंगे , और संभवतः __iter__इस उत्तर के अंत में वर्णित जनरेटर के रूप में उसी रूप के जनरेटर फ़ंक्शन के रूप में फिर से परिभाषित करें (सीमा के बजाय को छोड़कर) तर्कों से आने के बाद __iter__, वे दलीलों को __init__सहेजने selfऔर उन पर पहुंचने से बचते selfहैं __iter__)।
शैडो रेंजर

427

पुनरावृत्त समारोह बनाने के चार तरीके हैं:

उदाहरण:

# generator
def uc_gen(text):
    for char in text.upper():
        yield char

# generator expression
def uc_genexp(text):
    return (char for char in text.upper())

# iterator protocol
class uc_iter():
    def __init__(self, text):
        self.text = text.upper()
        self.index = 0
    def __iter__(self):
        return self
    def __next__(self):
        try:
            result = self.text[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return result

# getitem method
class uc_getitem():
    def __init__(self, text):
        self.text = text.upper()
    def __getitem__(self, index):
        return self.text[index]

कार्रवाई में सभी चार तरीकों को देखने के लिए:

for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
    for ch in iterator('abcde'):
        print(ch, end=' ')
    print()

जिसके परिणामस्वरूप:

A B C D E
A B C D E
A B C D E
A B C D E

नोट :

दो जनरेटर प्रकार ( uc_genऔर uc_genexp) नहीं हो सकते हैं reversed(); प्लेन इट्रेटर ( uc_iter) को __reversed__मैजिक मेथड की आवश्यकता होगी (जो डॉक्स के अनुसार , एक नया इटरेटर अवश्य लौटाए, लेकिन लौटाने के selfकाम (कम से कम सीपीथॉन में)); और गेटिटेम चलने योग्य ( uc_getitem) में __len__जादू की विधि होनी चाहिए :

    # for uc_iter we add __reversed__ and update __next__
    def __reversed__(self):
        self.index = -1
        return self
    def __next__(self):
        try:
            result = self.text[self.index]
        except IndexError:
            raise StopIteration
        self.index += -1 if self.index < 0 else +1
        return result

    # for uc_getitem
    def __len__(self)
        return len(self.text)

कर्नल पैनिक के अनन्त आलसी मूल्यांकन के बारे में माध्यमिक प्रश्न का उत्तर देने के लिए, ऊपर दिए गए चार तरीकों में से प्रत्येक का उपयोग करते हुए, यहां वे उदाहरण दिए गए हैं:

# generator
def even_gen():
    result = 0
    while True:
        yield result
        result += 2


# generator expression
def even_genexp():
    return (num for num in even_gen())  # or even_iter or even_getitem
                                        # not much value under these circumstances

# iterator protocol
class even_iter():
    def __init__(self):
        self.value = 0
    def __iter__(self):
        return self
    def __next__(self):
        next_value = self.value
        self.value += 2
        return next_value

# getitem method
class even_getitem():
    def __getitem__(self, index):
        return index * 2

import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
    limit = random.randint(15, 30)
    count = 0
    for even in iterator():
        print even,
        count += 1
        if count >= limit:
            break
    print

जिसके परिणामस्वरूप (कम से कम मेरे सैंपल रन के लिए):

0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32

कैसे चुनें कि किसका उपयोग करना है? यह ज्यादातर स्वाद का मामला है। जिन दो तरीकों को मैं सबसे अधिक बार देखता हूं, वे हैं जनरेटर और इटरेटर प्रोटोकॉल, साथ ही एक हाइब्रिड ( __iter__एक जनरेटर की वापसी)।

जेनरेटर अभिव्यक्तियाँ सूची बोध को बदलने के लिए उपयोगी हैं (वे आलसी हैं और इसलिए संसाधनों पर बचत कर सकते हैं)।

यदि किसी को पहले पायथन 2.x संस्करणों के साथ संगतता की आवश्यकता होती है __getitem__


4
मुझे यह सारांश पसंद है क्योंकि यह पूर्ण है। वे तीन तरीके (उपज, जनरेटर अभिव्यक्ति और पुनरावृत्ति) अनिवार्य रूप से समान हैं, हालांकि कुछ दूसरों की तुलना में अधिक सुविधाजनक हैं। उपज ऑपरेटर "निरंतरता" को पकड़ता है जिसमें राज्य होता है (उदाहरण के लिए सूचकांक जो हम ऊपर हैं)। जानकारी निरंतरता के "बंद" में सहेजी जाती है। इटरेटर रास्ता सूचनाओं के क्षेत्रों के अंदर वही जानकारी बचाता है, जो अनिवार्य रूप से एक क्लोजर के समान है। GetItem विधि थोड़ा अलग है क्योंकि यह सामग्री में अनुक्रमित और पुनरावृत्ति प्रकृति में नहीं है।
इयान

2
@metaperl: वास्तव में, यह है। उपरोक्त सभी चार मामलों में आप समान कोड का उपयोग कर सकते हैं।
एथन फुरमान

1
@ एस्टरिस्क: नहीं, ऐसा uc_iterहोने पर एक उदाहरण समाप्त हो जाना चाहिए (अन्यथा यह अनंत द्वारा होगा); यदि आप इसे फिर से करना चाहते हैं तो आपको फिर से कॉल करके एक नया पुनरावृत्ति प्राप्त uc_iter()करना होगा।
एतान फुरमान

2
आप सेट कर सकते self.index = 0में __iter__है, ताकि आप में कई बार पुनरावृति कर सकते हैं। अन्यथा आप नहीं कर सकते।
जॉन स्ट्रोड

1
यदि आप उस समय को छोड़ सकते हैं तो मैं इस स्पष्टीकरण की सराहना करूंगा कि आप दूसरों पर कोई भी तरीका क्यों चुनेंगे।
आआआआआआआआआना

103

सबसे पहले सभी इटर्टूल मॉड्यूल उन सभी प्रकार के मामलों के लिए अविश्वसनीय रूप से उपयोगी होते हैं, जिनमें एक इटरेटर उपयोगी होगा, लेकिन यहाँ आपको अजगर में एक इटरेटर बनाने की आवश्यकता है:

प्राप्ति

क्या यह अच्छा नहीं है? किसी फ़ंक्शन में सामान्य रिटर्न को बदलने के लिए यील्ड का उपयोग किया जा सकता है । यह वस्तु को उसी तरह लौटाता है, लेकिन राज्य को नष्ट करने और बाहर निकलने के बजाय, जब आप अगली पुनरावृत्ति को निष्पादित करना चाहते हैं, तो यह राज्य को बचाता है। यहाँ इसका एक उदाहरण है इटर्टलस फ़ंक्शन सूची से सीधे खींची गई क्रिया :

def count(n=0):
    while True:
        yield n
        n += 1

जैसा कि फ़ंक्शन विवरण में कहा गया है (यह इटर्टल्स मॉड्यूल ... से गिनती () फ़ंक्शन है), यह एक इट्रेटर पैदा करता है जो एन के साथ शुरू होने वाले लगातार पूर्णांक देता है।

जेनरेटर के भाव कीड़े (भयानक कीड़े!) की एक पूरी दूसरी कैन हैं। स्मृति को सहेजने के लिए सूची बोध के स्थान पर उनका उपयोग किया जा सकता है (सूची बोधक स्मृति में एक सूची बनाते हैं जो उपयोग के बाद नष्ट हो जाती है यदि एक चर को नहीं सौंपा गया है, लेकिन जनरेटर के भाव एक जेनरेटर ऑब्जेक्ट बना सकते हैं ... जो कि फैंसी तरीका है Iterator कह रहा है)। यहाँ एक जनरेटर अभिव्यक्ति परिभाषा का एक उदाहरण है:

gen = (n for n in xrange(0,11))

यह पूरी तरह से 0 और 10 के बीच होने के अलावा पूर्व सीमा को छोड़कर हमारी इटेरेटर परिभाषा के समान है।

मैंने अभी-अभी xrange () पाया ( इससे पहले मैंने इसे नहीं देखा था ...) और इसे उपरोक्त उदाहरण में जोड़ा। xrange () रेंज का एक पुनरावृत्त संस्करण है () जिसमें सूची का पुनर्निर्माण नहीं करने का लाभ है। यह बहुत उपयोगी होगा यदि आपके पास डेटा का एक विशाल कोष इसे पुनरावृत्त करने के लिए है और केवल इतना मेमोरी है कि इसे करना है।


20
अजगर 3.0 के रूप में वहाँ अब एक xrange () और नई रेंज () पुराने xrange की तरह व्यवहार करता है ()

6
आपको अभी भी 2._ में xrange का उपयोग करना चाहिए, क्योंकि 2to3 स्वचालित रूप से इसका अनुवाद करता है।
फोब

100

मैं आप में से कुछ कर देखने return selfमें __iter__। मैं सिर्फ यह नोट करना चाहता था कि __iter__खुद एक जनरेटर हो सकता है (इस प्रकार अपवाद की आवश्यकता को दूर करना __next__और बढ़ाना StopIteration)

class range:
  def __init__(self,a,b):
    self.a = a
    self.b = b
  def __iter__(self):
    i = self.a
    while i < self.b:
      yield i
      i+=1

बेशक यहाँ एक सीधे तौर पर एक जनरेटर बना सकता है, लेकिन अधिक जटिल वर्गों के लिए यह उपयोगी हो सकता है।


5
महान! यह इतना उबाऊ लेखन सिर्फ return selfमें __iter__। जब मैं इसका उपयोग करने की कोशिश करने जा रहा था yieldतो मैंने पाया कि आपका कोड वही है जो मैं कोशिश करना चाहता हूं।
रे

3
लेकिन इस मामले में, कोई कैसे लागू करेगा next()? return iter(self).next()?
Lenna

4
@ लेन्ना, यह पहले से ही "कार्यान्वित" है क्योंकि iter (स्व) एक पुनरावृत्त लौटाता है, न कि एक श्रेणी उदाहरण।
मानक्स

3
यह करने का सबसे आसान तरीका है, और उदाहरण के लिए self.currentया किसी अन्य काउंटर का ट्रैक रखने में शामिल नहीं है । यह शीर्ष मतदान का जवाब होना चाहिए!
एस्ट्रोफ्रॉग

4
स्पष्ट है कि, इस दृष्टिकोण अपने वर्ग में आता है iterable , लेकिन नहीं एक इटरेटर । हर बार जब आप कक्षा के उदाहरणों पर कॉल करते हैं, तो आपको नए पुनरावृत्तियों मिलते हैं iter, लेकिन वे स्वयं कक्षा के उदाहरण नहीं होते हैं।
शैडो रेंजर

13

यह सवाल पुनरावृत्ति वाली वस्तुओं के बारे में है, न कि पुनरावृत्तियों के बारे में। पायथन में, अनुक्रम भी पुनरावृत्त होते हैं, इसलिए एक पुनरावृत्त वर्ग बनाने का एक तरीका यह है कि इसे एक अनुक्रम की तरह व्यवहार किया जाए, अर्थात इसे __getitem__और __len__तरीके दें। मैंने पायथन 2 और 3 पर इसका परीक्षण किया है।

class CustomRange:

    def __init__(self, low, high):
        self.low = low
        self.high = high

    def __getitem__(self, item):
        if item >= len(self):
            raise IndexError("CustomRange index out of range")
        return self.low + item

    def __len__(self):
        return self.high - self.low


cr = CustomRange(0, 10)
for i in cr:
    print(i)

1
इसका कोई __len__()तरीका नहीं है। __getitem__अकेले के साथ अपेक्षित व्यवहार पर्याप्त है।
ब्लैकजैक

5

इस पृष्ठ पर सभी उत्तर वास्तव में एक जटिल वस्तु के लिए महान हैं। लेकिन गुणों के रूप में iterable प्रकार निर्मित युक्त उन लोगों के लिए, की तरह str, list, setया dict, या के किसी भी कार्यान्वयन collections.Iterable, आप कुछ चीजें अपनी कक्षा में छोड़ सकते हैं।

class Test(object):
    def __init__(self, string):
        self.string = string

    def __iter__(self):
        # since your string is already iterable
        return (ch for ch in self.string)
        # or simply
        return self.string.__iter__()
        # also
        return iter(self.string)

इसका उपयोग इस तरह किया जा सकता है:

for x in Test("abcde"):
    print(x)

# prints
# a
# b
# c
# d
# e

1
जैसा कि आपने कहा, स्ट्रिंग पहले से ही चलने योग्य है इसलिए क्यों अतिरिक्त जनरेटर अभिव्यक्ति के बीच में केवल स्ट्रिंगर के लिए स्ट्रिंग पूछना (जो जनरेटर अभिव्यक्ति आंतरिक रूप से करता है) return iter(self.string):।
ब्लैकजैक

@ ब्लेकजैक आप वास्तव में सही हैं। पता नहीं किस बात ने मुझे इस तरह लिखने के लिए राजी किया। शायद मैं अधिक पुनरावृत्त वाक्यविन्यास के संदर्भ में पुनरावृत्ति वाक्यविन्यास की व्याख्या करने की कोशिश कर रहे उत्तर में किसी भी भ्रम से बचने की कोशिश कर रहा था।
जॉन स्ट्रॉड

3

यह बिना चलने वाला कार्य है yield। यह iterफ़ंक्शन और एक क्लोजर का उपयोग करता है जो इसे listअजगर 2 के लिए संलग्न करने के दायरे में एक उत्परिवर्तनीय ( ) स्थिति में रखता है ।

def count(low, high):
    counter = [0]
    def tmp():
        val = low + counter[0]
        if val < high:
            counter[0] += 1
            return val
        return None
    return iter(tmp, None)

पायथन 3 के लिए, क्लोजर स्टेट को एनक्लोजिंग स्कोप में अपरिवर्तनीय रखा जाता है और nonlocalस्टेट वेरिएबल को अपडेट करने के लिए लोकल स्कोप में उपयोग किया जाता है।

def count(low, high):
    counter = 0
    def tmp():
        nonlocal counter
        val = low + counter
        if val < high:
            counter += 1
            return val
        return None
    return iter(tmp, None)  

परीक्षा;

for i in count(1,10):
    print(i)
1
2
3
4
5
6
7
8
9

मैं हमेशा दो-आरजी के एक चतुर उपयोग की सराहना करता हूं iter, लेकिन सिर्फ स्पष्ट होने के लिए: यह एक yieldआधारित जनरेटर फ़ंक्शन का उपयोग करने की तुलना में अधिक जटिल और कम कुशल है ; पायथन में yieldआधारित जनरेटर फ़ंक्शंस के लिए एक टन इंटरप्रेटर समर्थन है जो आप यहाँ का लाभ नहीं उठा सकते हैं, जिससे यह कोड काफी धीमा हो जाता है। फिर भी गैर-मतदान किया।
शैडो रेंजर

2

यदि आप कुछ छोटा और सरल खोज रहे हैं, तो शायद यह आपके लिए पर्याप्त होगा:

class A(object):
    def __init__(self, l):
        self.data = l

    def __iter__(self):
        return iter(self.data)

उपयोग का उदाहरण:

In [3]: a = A([2,3,4])

In [4]: [i for i in a]
Out[4]: [2, 3, 4]

-1

मैट ग्रेगोरी के जवाब से प्रेरित एक और अधिक जटिल पुनरावृत्ति है जो ए, बी, ..., जेड, आ, एबी, ..., ज़ज़, आआ, आब, ..., ज़ज़ी, ज़ज़ लौट आएगा।

    class AlphaCounter:
    def __init__(self, low, high):
        self.current = low
        self.high = high

    def __iter__(self):
        return self

    def __next__(self): # Python 3: def __next__(self)
        alpha = ' abcdefghijklmnopqrstuvwxyz'
        n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
        n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
        if n_current > n_high:
            raise StopIteration
        else:
            increment = True
            ret = ''
            for x in self.current[::-1]:
                if 'z' == x:
                    if increment:
                        ret += 'a'
                    else:
                        ret += 'z'
                else:
                    if increment:
                        ret += alpha[alpha.find(x)+1]
                        increment = False
                    else:
                        ret += x
            if increment:
                ret += 'a'
            tmp = self.current
            self.current = ret[::-1]
            return tmp

for c in AlphaCounter('a', 'zzz'):
    print(c)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.