Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A simple Python iterator going in to infinite loop

I was trying to write a simple countdown iterator of my own, I implemented a __iter__() function and the corresponding __next__() to support the iterator.I have used a yield function inside the __next__() function to return a new value everytime I iterate over the object. When I use yield, the code goes over in an infinite loop as compared to using return statement. Following is my code:

class MyIterator():
    def __init__(self,value):
        self.value = value
    def __iter__(self):
        return self
    def __next__(self):
        print("In the next function")
        if self.value > 0:
            yield self.value
            self.value -= 1
        else:
            raise StopIteration("Failed to proceed to the next step")



if __name__ == '__main__':
    myIt = MyIterator(10)
    for i in myIt:
        print(i)

And the O/P is as follows:

  <generator object __next__ at 0x101181990>
  <generator object __next__ at 0x1011818e0>
  <generator object __next__ at 0x101181990>
  <generator object __next__ at 0x1011818e0>

  and so on for infinite times....
like image 981
Aparna Chaganti Avatar asked Jul 24 '26 14:07

Aparna Chaganti


1 Answers

Your __next__ method should not itself be a generator. Replace yield with return:

def __next__(self):
    print("In the next function")
    if self.value > 0:
        return_value = self.value
        self.value -= 1
        return return_value
    else:
        raise StopIteration("Failed to proceed to the next step")

Note that you still need to decrease self.value after determining what is to be returned, hence the use of a separate return_value variable.

Any function (or method) with yield in it will produce a generator object when called, and that generator is then the iterable. Such an object then has an __iter__ method that returns self and a __next__ method that will produce the next value when called. This is why you see the <generator object __next__ at 0x1011818e0> object being printed each time __next__ is called.

However, for your object to be an iterable itself, your __next__ method should instead return the next value in the sequence. It'll be called repeatedly until it raises StopIteration. This is different from using yield, it should return immediately, not defer until later!

Demo:

>>> class MyIterator():
...     def __init__(self,value):
...         self.value = value
...     def __iter__(self):
...         return self
...     def __next__(self):
...         print("In the next function")
...         if self.value > 0:
...             return_value = self.value
...             self.value -= 1
...             return return_value
...         else:
...             raise StopIteration("Failed to proceed to the next step")
...
>>> myIt = MyIterator(10)
>>> for i in myIt:
...     print(i)
...
In the next function
10
In the next function
9
In the next function
8
In the next function
7
In the next function
6
In the next function
5
In the next function
4
In the next function
3
In the next function
2
In the next function
1
In the next function

If you wanted to use a generator function, make __iter__ the generator, and use a loop:

class MyIterator():
    def __init__(self,value):
        self.value = value
    def __iter__(self):
        value = self.value
        while value > 0:
            yield value
            value -= 1

However, this makes your MyIterator class an iterable, not an iterator. Instead, each time you use a for loop a new iterator is created (the __iter__ generator object) that is then iterated over. Using __next__ makes your object an iterator that can only be iterated over once.

like image 76
Martijn Pieters Avatar answered Jul 27 '26 05:07

Martijn Pieters



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!