0

So I understand that sometimes instead of defining iter and next methods within a class that is supposed to be iterable, using just an iter method containing a yield statement suffices. Actually why? Just do avoid boilerplate code?

However, I dont get why the following snippet yields three iterations

class BoundedRepeater:
    def __init__(self, value, max_repeats):
        self.value = value
        self.max_repeats = max_repeats
        self.count = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.count >= self.max_repeats:
            raise StopIteration
        self.count += 1
        return self.value

if called like this

for item in BoundedRepeater("Hello", 3):
    print(item)

but if I change the methods to

class BoundedRepeater: def init(self, value, max_repeats): self.value = value self.max_repeats = max_repeats self.count = 0

class BoundedRepeater:
    def __init__(self, value, max_repeats):
        self.value = value
        self.max_repeats = max_repeats
        self.count = 0

    def __iter__(self):
        if self.count >= self.max_repeats:
            raise StopIteration
        self.count += 1
        yield self.value

I only get one iteration instead of three

CD86
  • 979
  • 10
  • 27
  • 2
    The 2nd code should have a loop: `for _ in range(self.max_repeats): yield self.value`. [What's the advantage of using yield in __iter__()?](https://stackoverflow.com/questions/45685538/whats-the-advantage-of-using-yield-in-iter) – 001 Nov 14 '19 at 12:01

2 Answers2

2

The second example gives you only one iteration because the yield statement is called only once, because __iter__ is called only once. The __iter__ method does not return the next value (and therefore is not called once per value), it returns an iterator that has all the values.

You could write

class SmallPrimes:
    def __iter__(self):
        yield 2
        yield 3
        yield 5
        yield 7

which shows that one single call to iter contains all the values.

In your case, the BoundedRepeater class would put the yield in a for-loop:

class BoundedRepeater:
    def __init__(self, value, max_repeats):
        self.value = value
        self.max_repeats = max_repeats

    def __iter__(self):
        for _ in range(self.max_repeats):
            yield self.value

The execution of __iter__ is paused during each yield, and then resumes when the next value is required. During that pause, all the context is kept. Note how there is no need to keep track of a counting variable (self.count in your first example).

For your specific example, which is about repeating a value V a number N of times, you do not need to write your own class. You can just write a function:

def bounded_repeat(V, N):
    for _ in range(N):
        yield V

for v in bounded_repeat(V, N):
    y = do_something(v)

But you do not need to write that function because it already exists:

import itertools
for v in itertools.repeat(V, N):
    y = do_something(v)

However, unless V is heavy and N is huge, just stick to some good old Python:

for v in [V] * N:
    y = do_something(v)
Niriel
  • 2,605
  • 3
  • 29
  • 36
0

You can use a generator function, but you should yield results in a loop, in your case bounded by the number max_repeat

I think your __iter__ method should be written as follow:

    def __iter__(self):
        for i in range(self.max_repeats):
            yield self.value
        return

I tried and it generates as many items as specified in max_occurences.

br = BoundedRepeater('tagada', 5)
for item in br:
    print(item)

# ->tagada
# ->tagada
# ->tagada
# ->tagada
# ->tagada
alfajet
  • 389
  • 1
  • 14
  • thank you guys. My question aimed more at the low-level details in the way that I wonder why there is no need for an internal loop in the next method of the first implementation, whereas I do need one in the second version. I still have trouble wrapping my head around that. – CD86 Nov 14 '19 at 12:28
  • So if I understand correctly, iter is only called once if the iterable is used e.g. in the for loop. But next of the first implementation is called until the exception is raised. All hidden in the loop syntax. For the yield version with the correct loop syntax, control is temporarily passed back to the call-site after iter was called once and only once. However, upon the next loop iteration, control is given back to the __iter__ method, even without being called explicitly, to yield the next value. Did I get that right? – CD86 Nov 14 '19 at 12:32