CD86
CD86

Reputation: 1099

Generators as iterator member functions

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

Upvotes: 0

Views: 152

Answers (2)

Niriel
Niriel

Reputation: 2705

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)

Upvotes: 2

alfajet
alfajet

Reputation: 439

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

Upvotes: 0

Related Questions