10

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.

vgel
  • 3,225
  • 1
  • 21
  • 35
  • 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? – SingleNegationElimination Feb 26 '14 at 00:08
  • 1
    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. – roippi Feb 26 '14 at 00:14
  • @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? – vgel Feb 26 '14 at 00:42
  • because generators in python don't have distinct `send()` and `next()`, `send()` is just `next()` with an argument. – SingleNegationElimination Feb 26 '14 at 01:06

2 Answers2

3

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
The Godfather
  • 4,235
  • 4
  • 39
  • 61
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
bj0
  • 7,893
  • 5
  • 38
  • 49
  • 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. – vgel Feb 26 '14 at 01:40