Python generator send: don't yield a new value after a send
Asked Answered
T

2

12

This is kind of a weird question so I'll explain:

I have a generator like this that is acting as a generator frontend to an IRC server:

def irc_iter(): # not the real code, simplified
    msgs = get_msgs()
    for msg in msgs:
        if is_ping(msg):
            pong()
        else:
            to_send = yield msg
            for s in to_send:
                send(s)

This, theoretically, should allow me to do something cool, like:

server = connect()
for line in server:
       if should_respond(line):
           server.send('WOW SUCH MESSAGE')

However, there's a hitch: generator.send yields the next value as well. This means that server.send is also giving me the next message... which I would prefer to handle like all the other messages, yielded as line.

I know I can fix this in an ugly way, by just yielding a junk value after receiving a send, but I'm trying to keep my code elegant and that is the opposite. Is there a way to just tell the generator I don't want a new value yet?

Thanks.

Tillandsia answered 25/2, 2014 at 23:59 Comment(4)
I'm not sure I see where the problem is. getting data from a server is not the same idea as sending data to it. do you really need two coroutines?Geostatic
unfortunately, yeah, I often end up putting bare yield statements in my coroutines when they are this "composite" style of half-iterator, half-coroutine. I've had some success in rewriting such consumers to explicitly send all of the time, even if I'm sending None the majority of the time. Not sure if that helps you here, but it's an idea.Everglades
@IfLoop But communicating with the server is the same thing as communicating... with the server. The direction of communication is already cleanly expressed by the direction of data to/from the coroutine. Why split it up?Tillandsia
because generators in python don't have distinct send() and next(), send() is just next() with an argument.Geostatic
G
4

I also just came across this issue and also didn't find anything better than dummy yield...

So in your generator code:

        # Input
        input_value = yield
        if input_value is None:
            # Handle situation if someone used your generator in a wrong way
            sys.exit(-1)
        yield "DUMMY YIELD JUST STOP HERE!!!"

And in the client code:

while True:
    values = next(my_generator)
    ...
    if values == 'INPUT_NEEDED':
        # At this point you realize it's input time
        next(my_generator) # Rewind to the input point
        my_generator.send(12345) # Returns "DUMMY YIELD JUST STOP HERE!!!"
        continue
    # Continue the main loop
Gazo answered 15/12, 2019 at 11:6 Comment(0)
L
1

It looks like the problem comes from you calling the generator twice per iteration, once with .send(None) (in the for loop) and once with .send(response).

It would be a simple fix if the for loop could iterate over .send() instead of .next(), but I am not familiar with any way to get that to work (optional extended continue statement in pep342?) without wrapping it in another generator (possibly using a queue to push values into .send() on .next() calls). The simplest solution, though, would probably be:

server = connect()
response = None 
try:
    while True:
        line = server.send(response)
        response = None
        if should_respond(line):
            response ='WOW SUCH MESSAGE'
except StopIteration:
    pass
Lebna answered 26/2, 2014 at 1:17 Comment(1)
That solution works, but not much better than just having a dummy yield. I was hoping there was simply an alternative method call or library that could handle this.Tillandsia

© 2022 - 2024 — McMap. All rights reserved.