Tanzer
Tanzer

Reputation: 320

Python generator send() strange behaviour

While I try to understand the mechanism of generator's send() method, I encountered a strange behaviour in my example. (VS Code Python 3.10)

def MultipleOfThree():
    num  = 1
    while True:
        if num % 3 == 0:
           yield num
        num += 1

#test
iter = MultipleOfThree()

for m3 in iter:    
    print(m3)

This code is working as expected and prints

>>> 3,6,9,12,15,18,21.....

When I add the send() statement in for loop, and arrange the MultipleOfThree() func like below

def MultipleOfThree():
    num  = 1

    while True:
        if num % 3 == 0:
            i = yield num   
            if i is not None:
                num = i
        num += 1

#test
iter = MultipleOfThree()

for m3 in iter:    
    print(m3)
    iter.send(m3) #for simplicity send the m3 itself

it prints

>>> 3,9,15,21,27

I couldn't understand the mechanism of send() here, why escapes the for loop. I study the subject from this page How to Use Generators and yield in Python.

Upvotes: 2

Views: 140

Answers (2)

VPfB
VPfB

Reputation: 17332

The generator makes a stop at each yield of some value. It continues after the value is consumed by calling next on the corresponding iterator. It continues until the next yield (or generator exit).

A for loop uses this iterator protocol to consume the yielded values one at a time and runs the loop body with each one.

A send is similar to next. It consumes a value, makes the generator to advance, but in addition to next It also sends a value to the generator. The sent value becomes the value returned by the yield (in the generator).

Your code is interacting with the generator at two places: in the for statement and in the send statement. But you are printing only one of the values, that's why you see only every second.

Add a print and you'll see all values again:

for m3 in iter:    
   print(m3)               # print 1 (yielded by for loop)
   print(iter.send(m3))    # print 2 (yielded by send)

Upvotes: 1

Bharel
Bharel

Reputation: 26981

First initialize the generator:

iter = MultipleOfThree()

Get the first value:

m3= next(iter)

Then continue in a while loop:

while True:
    print(m3)
    m3 = iter.send(m3)

You wish to get the first value using next(iter) or iter.send(None) in order to reach the first multiple of three, without any input.

Then, you want to send back the input and continue from there using iter.send(m3).

Overall code:

def MultipleOfThree():
    num  = 1
    while True:
        if num % 3 == 0:
            i = yield num   
            if i is not None:
                num = i
        num += 1

iter = MultipleOfThree()
m3= next(iter)
while True:
    print(m3)
    m3 = iter.send(m3)

Upvotes: 0

Related Questions