When I create a class based generator, why do I have to call the next? [duplicate]
When I create a class based generator, why do I have to call the next? [duplicate]
This question already has an answer here:
I'm new to Python and I'm reading the book Python Tricks. In the chapter about generators, it gives the following example (with some changes)
class BoundedGenerator:
def __init__(self, value, max_times):
self.value = value
self.max_times = max_times
self.count = 0
def __iter__(self):
return self
def __next__(self):
if self.count < self.max_times:
self.count += 1
yield self.value
After that, I write a loop, instantiate the generator and print the value:
for x in BoundedGenerator('Hello world', 4):
print(next(x))
Why do I have to call the next(X) inside the loop?
next(X)
I (think) I understand that the __iter__ function will be called in the loop line definition and the __next__ will be called in each iteration, but I don't understand why I have to call the next again inside the loop. Is this not redundant?
If I don't call the __next__ function, my loop will run forever.
__iter__
__next__
__next__
This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.
__iter__
__next__
Quite frankly, this is a terrible and confusing example if it comes from that book, it muddies the water on generators, iterators, and iterable (three distinct albeit related concepts)
– juanpa.arrivillaga
Sep 8 '18 at 18:57
Hi, thanks for the explanation and your time. Just to try to show the author's point, he starts with this kind of example but goes along and uses generator functions and generator expression and he gives the same opinion that I could achieve the same without this boilerplate code using one of the two other options (expressions or functions).
– Thiago
Sep 9 '18 at 14:26
1 Answer
1
Your __next__ method itself is a generator function due to using yield. It must be a regular function that uses return instead.
__next__
yield
return
def __next__(self):
if self.count < self.max_times:
self.count += 1
return self.value # return to provide one value on call
raise StopIteration # raise to end iteration
When iterating, python calls iter.__next__ to receive the new value. If this is a generator function, the call merely returns a generator. This is the same behaviour as for any other generator function:
iter.__next__
>>> def foo():
... yield 1
...
>>> foo()
<generator object foo at 0x106134ca8>
This requires you to call next on the generator to actually get a value. Similarly, since you defined BoundedGenerator.__next__ as a generator function, each iteration step provides only a new generator.
next
BoundedGenerator.__next__
Using return instead of yield indeed returns the value, not a generator yielding said value. Additionally, you should raise StopIteration when done - this signals the end of the iteration.
return
yield
raise StopIteration
Thank you for your time and explanation. That makes sense to me know.
– Thiago
Sep 9 '18 at 14:33
This is not jow you use generators, generators exist to allow you to write iterators without the class-overhead. So, a generator function makes a great
__iter__method for a iterable (or any method you want to return an iterator from), but it doesn't make sense as the__next__method of an iterator class, unless you want your iterator to return other iterators.– juanpa.arrivillaga
Sep 8 '18 at 18:53