Generator - Coroutines in Python Best Practices

De openkb
Aller à : Navigation, rechercher

Sommaire

Questions

I am wondering what the best practices are for writing coroutines in Python 3. I am developing basic methods which should accept some input (using the .send() method), perform computations on this input, and then yield output.

The first approach I found is to essentially do the following:

def coroutine(func):
  data = yield
  while 1:
    data = yield func(data)

That seems to work, but the line in the loop is bending my mind. It appears to first yield a function, and then take input and perform the assignment after resuming. This is completely non-intuitive to me.

The other approach I m looking at is:

def coroutine():
  while 1:
    data = yield
    [ do stuff with data here ... ]
    yield result

This code is much easier for me to understand, and it also lets me put code right into the generator instead of passing in a function. But it s annoying to use. Every actual call to the generator (like "gen.send(2)") has to be followed by a "gen.send(None)" to advance the generator to the next yield.

It seems to me like the problem here stems from the "yield" keyword being used for two different things: a return statement, and an input statement.

If possible I want an approach that lets me take input, do calculations on that input, and then yield output, without having to pass in functions and use one-liners as in the first approach, or having to send extraneous values as in the second approach. How can I do this?


Please note: In reality, I will be sending in multiple values. So the problems of having extraneous "g.send(None)" statements get worse.

Answers

You can do it as you did in your first example. You just have to "do stuff with data" inside the loop. Here is an example:

def coroutine():
  data = yield
  while True:
    print("I am doing stuff with data now")
    data = data * 2
    data = yield data

You can use it like this:

>>> co = coroutine()
>>> next(co)
>>> co.send(1)
I am doing stuff with data now
2
>>> co.send(88)
I am doing stuff with data now
176

You are correct that yield plays a dual role, both yielding a result out and accepting the value subsequently passed in via send. (Likewise, send plays a dual and complementary role, in that every send call returns the value that the generator yields.) Note the order there: when you have a yield expression, it first yields the value out, and then the value of the yield expression becomes whatever is sent in afterwards .

This may seem "backwards", but you can get it to be "forwards" by doing it in a loop, as you essentially already did. The idea is that you first yield some initial value (maybe a meaningless one). This is necessary because you can t use send before a value has been yielded (since there would be no yield expression to evaluate to the sent value). Then, every time you use yield, you are giving out the "current" value, while simultaneously accepting input to be used in computing the "next" value.

As I mentioned in a comment, it is not clear from your example why you re using generators at all. In many cases, you can achieve a similar effect just by writing a class that has its own methods for passing things in and getting things out, and if you write the class, you can make the API whatever you want. If you choose to use generators, you have to accept the dual input/output roles of send and yield. If you don t like that, don t use generators (or, if you need the suspended function-state they provide, you can use them but wrap them with a class that separates the sending from the yielding).

Source

License : cc by-sa 3.0

http://stackoverflow.com/questions/34101084/coroutines-in-python-best-practices

Related

Outils personnels
Espaces de noms

Variantes
Actions
Navigation
Outils