Use the Continue (or Pass) statement to send values to a generator

The basic idea

Generators can receive values from the yield <value> statement by using generator.send(value). I think this would be a decent QoL feature to make it easier to pass values to generators by using pass or continue

Cons of using the continue <value> syntax

continue <value, in order to make sense, would just need to be a logical extension of the already existing continue statement. That means that it must also work like the regular continue statement and skip to the next iteration.

Cons of using the pass <value> syntax

pass normally does absolutely nothing. Adding functionality to pass could be seen as weird, but the word pass makes sense logically if you are “passing” a value back to the generator. Since the pass statement also does nothing normally, there is no functionality to compare the new pass <value> syntax to.

Pros of both

This is simply a QoL feature that would allow you to send send data to a generator without first storing that generator somewhere.

Examples

previous way

import random

def my_gen():
    '''A generator that receives values'''
    pass

gen = my_gen()

for x in gen:
    gen.send(random.randint(0, 100))

New way

import random

def my_gen():
    '''A generator that receives values'''
    pass

for x in my_gen():
    pass random.randint(0, 100)   # alternatively, `continue random.randint(0, 100)`

edit:

clarification

what about nested loops?

In that case, the pass <value> statement will assume you want to send the the value to the generator in the current loop. This means it makes the same assumption as the break statement. If you want to use the old syntax to add specificity, go ahead, it isn’t being removed by my idea.

for x in my_gen(): # loop 1
    # ...
    for y in my_gen(): # loop 2
        pass 8 # value passed to loop 2
    # ...
    pass 8 # value passed to loop 1

what about nested generators?

example of what I mean when I say “nested generators”

for tup in zip(spamgen, eggsgen, cheesegen):
    pass value

In this case, the value is passed to the top-level generator and not its children (if that makes any sense). For example, in the code above, the value would be passed to the zip() generator, not the children spamgen, eggsgen, and cheesegen.

How do you know which generator the value is sent to?

That means Python somehow has to magically know which generator you’re sending to. That seems a bit dubious.

Maybe there’s some other way. Can you post a non-trivial use case? I can imagine designing coroutines with a driver of a sort, such that your loop instead becomes another generator; the driver could basically go “get a value from this one, send it to that one, get a value from that one, send it to this one”:

def user_interface():
    while True:
        result = yield(input("Enter a number: "))
        print("Result:", result)

def calculation():
    n = (yield ...)
    while True:
        result = n * n
        n = (yield result)

Neither of your actual coroutines needs to keep track of the generator, and the driver can take care of the brokering.

I assume your talking about nested for loops.

I don’t really think the question matters that much since we can’t even specify which loop we want to break out of when using the break statement, it just assumes the current loop. I think my suggestion could easily work in the same way.

for x in range(...):
    for y in range(...):
        ... # cannot easily break from both loops here, just one

If you place the loops inside a function you can return.

yes, I know that, but my point still stands about not being able to get finely tuned control using the break statement. I was making an example saying that what I suggested can make the same assumption as break which is that you want to break the current loop. The idea I proposed doesnt remove the current way to send values to a generator, it merely makes it slightly easier in simple use-cases.

On the positive side, it’s great to see a fellow fan of generator coroutines and send!

On the negative… what you call “a decent QoL feature” I call “a horrific nightmare”. I can already see ten thousand posts asking for help “where is my value being sent?” and “why isn’t my value being sent to the generator?”.

The pattern you are using:

gen = my_gen()

for x in gen:
    gen.send(random.randint(0, 100))

where you mix iteration and send is explicitly warned against as “mostly bogus” (overly-complicated, hard to understand, flakey, fragile). See slides 31-33.

You also assume that there is only ever one generator coroutine for the interpreter to send to, which is a bad assumption, and that it will always occur inside a loop, another bad assumption.

for tup in zip(spamgen, eggsgen, cheesegen):
    pass value

To be fair, that isn’t something you’re really able to send into. But I can imagine a coroutine-friendly zip that, when something is sent into it, sends the same value to all the subordinate generators. And if the OP’s idea were to be a thing, that would then work.

1 Like

Instead of pass value maybe continue generator with value would make more sense. It would also allow you to specify which generator to send the value to. To send a value to the iterator used in the currant for loop, continue with value which would work as currently proposed.

It’s spelled generator.send(value) :slight_smile: The OP’s proposal was to make it work implicitly with the current loop iterator, but if it’s going to be explicit, the existing method call is definitely better.

I found similar situation where this was useful to me so I had to return to while syntax and based on the changes I had to implement and the problems addressed it maybe makes more sense to implement this in the for statement itslef.

Adaptation for to while

for i in generator:
    ...

~=

there_is_next = True
while there_is_next :
    try:
         i = next(generator)
        ...
    except StopIteration:
        there_is_next = False

new while

there_is_next = True
next_value = None
while there_is_next :
    try:
         i = generator.send(next_value)
        ...
    except StopIteration:
        there_is_next = False

So if u translate this to

for i in generator passing value_to_process_by_generator:
    ...

Now is more clear where and what will send. Even you can add default value.

1 Like