[Python-ideas] Cofunctions - Back to Basics

Steven D'Aprano steve at pearwood.info
Fri Oct 28 03:01:07 CEST 2011


Nick Coghlan wrote:
> On Fri, Oct 28, 2011 at 5:33 AM, Paul Moore <p.f.moore at gmail.com> wrote:
>> On 27 October 2011 20:15, Arnaud Delobelle <arnodel at gmail.com> wrote:
>>
>>>> That article states that Python has coroutines as of 2.5 -- that's
>>>> incorrect, isn't it?
>>> Generator functions + trampoline = coroutines
>> If I understand, the cofunctions of this thread aren't coroutines as
>> such, they are something intended to make writing coroutines easier in
>> some way. My problem is that it's not at all obvious how they help.
>> That's partly because the generator+trampoline idiom, although
>> possible, is not at all common so that there's little in the way of
>> examples, and even less in the way of common understanding, of how the
>> idiom works and what problems there are putting it into practice.
> 
> I highly recommend reading the article Mark Shannon linked earlier in

If you're talking about this:

http://www.jucs.org/jucs_10_7/coroutines_in_lua/de_moura_a_l.pdf

I have read it, and while all very interesting, I don't see how it 
answers the big questions about motivating use-cases for cofunctions as 
described in this PEP.

One specific thing I took out of this is that only the main body of a 
Python generator can yield. That is, if I write this:

def gen():
     def inner():
         yield 1
         yield 2
     yield 0
     inner()  # yield 1 and 2
     yield 3

it does not behave as I would like. Instead, I have to write this:

def gen():
     def inner():
         yield 1
         yield 2
     yield 0
     # In Python 3.3, the next 2 lines become "yield from inner()"
     for x in inner():
         yield x
     yield 3


I can see how that would be a difficulty, particularly when you move 
away from simple generators yielding values to coroutines that accept 
values, but isn't that solved by the "yield from" syntax?


> the thread. I confess I didn't finish the whole thing, but even the
> first half of it made me realise *why* coroutine programming in Python
> (sans Stackless or greenlet) is such a pain: *every* frame on the
> coroutine stack has to be a generator frame in order to support
> suspending the generator.

I understand that, or at least I *think* I understand that, but I don't 
understand what that implies in practice when writing Python code.

If all you are saying is that you can't suspend an arbitrary function at 
at arbitrary point, well, true, but that's a good thing, surely? The 
idea of a function is that it has one entry point, it does its thing, 
and then it returns. If you want different behaviour, don't use a function.

Or do you mean something else? Actual working Python code (or not 
working, as the case may be) would probably help.


> Ideally, we would like to make it possible to write code like this:
> 
>     def coroutine_friendly_io(*args, **kwds):
>         if in_coroutine():
>             return coyield AsychronousRequest(async_op, *args, **kwds)
>         else:
>             return sync_op(*args, **kwds)

Why would you want one function to do double duty as both blocking and 
non-blocking? Particularly when the two branches don't appear to share 
any code (at least not in the example as given). To me, this seems like 
"Do What I Mean" magic which would be better written as a pair of functions:

     def coroutine_friendly_io(*args, **kwds):
         yield from AsychronousRequest(async_op, *args, **kwds)

     def blocking_io(*args, **kwargs):
         return sync_op(*args, **kwds)



-- 
Steven



More information about the Python-ideas mailing list