Python Generators and Coroutines

I study coroutines and generators in different programming languages.

I was wondering if there is a cleaner way to combine the two coroutines implemented through generators than to return to the caller no matter what the called subscriber gives.

Let's say that we use the following convention: all outputs except the last return null, and the last returns the result of the coroutine. So, for example, we may have a coroutine that calls another:

def A(): # yield until a certain condition is met yield result def B(): # do something that may or may not yield x = bind(A()) # ... return result 

in this case, I want through bind (which may or may not be implemented, this question) the coprocessor B gives whenever A gives until A returns its final result, which is then assigned to x, allowing B to continue .

I suspect that the actual code should explicitly iterate A like this:

 def B(): # do something that may or may not yield for x in A(): () # ... return result 

which is pretty ugly and error prone ...

PS: this is for a game in which users of the language will be developers who write scripts (script = coroutine). Each character has a script associated with it, and there are many sub-scripts that are called by the main script; note that, for example, run_ship calls many times reach_closest_enemy, fight_with_closest_enemy, flee_to_allies, etc. All these sub-scripts must be called as you described above; This is not a problem for the developer, but for the designer, the less code they have to write better!

+7
source share
2 answers

Are you looking for something like this?

 def B(): for x in A(): if x is None: yield else: break # continue, x contains value A yielded 
+2
source

Edit: I recommend using Greenlet . But if you're interested in a clean Python approach, read on.

This is described in PEP 342 , but at first it is difficult to understand. I will try to explain how this works.

First, let me summarize what, in my opinion, is the problem you are really trying to solve.

Problem

You have a generator function column that calls other generator functions. What you really want is to be able to receive from the generator at the top and have an output that extends up to the stack.

The problem is that Python (at the language level) does not support real coroutines, but only generators. (But they can be implemented.) Real coroutines allows you to stop the entire set of function calls and switch to another stack. Generators only allow one function to stop. If the generator f () wants to yield, the yield statement must be in f (), and not in another function that f () calls.

The solution that I think you're using right now is to do something like Simon Stelling's answer (i.e., call f () g (), getting all the results of g (). It's very verbose and ugly and you're looking for syntactic sugar to wrap this pattern in. Note that this essentially unwinds the stack every time you give in and then rewinds it again.

Decision

There is a better way to solve this problem. Basically, you execute coroutines by launching your generators on top of the trampoline system.

To do this, you need to complete a couple of patterns: 1. When you want to call another coroutine, give it. 2. Instead of returning a value, print it.

So

 def f(): result = g() # … return return_value 

becomes

 def f(): result = yield g() # … yield return_value 

Say you are in f (). The trampoline system is called f (). When you give a generator (e.g. g ()), the trampoline system calls g () on your behalf. Then, when g () finishes giving values, the trampoline system restarts f (). This means that you are not actually using the Python stack; instead, the trampoline system controls the still image.

When you get something other than a generator, the trampoline system treats it as a return value. It passes this value back to the caller's generator through the yield statement (using the generators .send () method).

Comments

Such a system is extremely important and useful in asynchronous applications such as Tornado or Twisted. You can stop the entire column when it is locked, go do something else, and then go back and continue with the first call where it stopped.

The disadvantage of the above solution is that it requires you to basically record all your functions as generators. It might be better to use a true coroutine implementation for Python - see below.

Alternatives

There are several coroutine implementations for Python: http://en.wikipedia.org/wiki/Coroutine#Implementations_for_Python

Greenlet is a great choice. This is a Python module that modifies the CPython interpreter to allow true coroutines by replacing a call.

Python 3.3 must provide delegation syntax to the subgenerator, see PEP 380 .

+16
source

All Articles