2

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?

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.

Cosmic Ossifrage
  • 4,977
  • 29
  • 30
Thiago
  • 864
  • 1
  • 9
  • 16
  • 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 08 '18 at 18:53
  • 1
    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 08 '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 09 '18 at 14:26

1 Answers1

4

Your __next__ method itself is a generator function due to using yield. It must be a regular function that uses return instead.

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:

>>> 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.

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.

MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119