From: Carl Banks on
On Jun 16, 5:03 am, Jérôme Mainka <jmai...(a)gmail.com> wrote:
> Hello,
>
> I try to experiment with coroutines and I don't understand why this
> snippet doesn't work as expected... In python 2.5 and python 2.6 I get
> the following output:
>
> 0
> Exception exceptions.TypeError: "'NoneType' object is not callable" in
> <generator object at 0x7e43f8> ignored
>
> The TypeError exception comes from the pprint instruction...
>
> If i replace the main part with:
>
> ==
> p1 = dump()
> p2 = sort(p1)
> for item in my_list: p2.send(item)
> ==
>
> it works as expected.
>
> I don't understand what is goind wrong. Has someone an explanation for
> this issue?
>
> Thanks,
>
> Jérôme
>
> ===
> from functools import wraps
> from pprint import pprint
> import random
>
> def coroutine(f):
>     @wraps(f)
>     def start(*args, **kwargs):
>         res = f(*args, **kwargs)
>         res.next()
>         return res
>     return start
>
> @coroutine
> def sort(target):
>     l = []
>
>     try:
>         while True:
>             l.append((yield))
>     except GeneratorExit:
>         l.sort()
>         for item in l:
>             target.send(item)
>
> @coroutine
> def dump():
>     while True:
>         pprint((yield))
>
> if __name__ == "__main__":
>     my_list = range(100)
>     random.shuffle(my_list)
>
>     p = sort(dump())
>
>     for item in my_list:
>         p.send(item)

Tricky, but pretty simple once you know what happens.

What's happening here is that the GeneratorExit exception isn't raised
until the generator is destroyed (because how does the generator know
there are no more sends coming?). That only happens when the __main__
module is being destroyed.

Well, when destroying __main__, Python overwrites all its attributes
with None, one-by-one, in arbitrary order. In the first scenario,
"pprint" was being set to None before "p" was, thus by the time the
generator got about to running, the pprint was None. In the second
scenario, "p2" was being set to None before "pprint", so that pprint
still pointed at the appropriate function when the generator began
executing.

The solution is to explicity close the generator after the loop, this
signaling it to run before __main__ is destroyed.

p.close()


I suggest, if you intend to use this kind of thing in real code (and I
would not recommend that) that you get in a habit of explicitly
closing the generator after the last send(), even when you don't think
you have to.

IMHO, coroutines are the one time during the PEP-era that Python can
be accused of feature creep. All other changes seemed driven by
thoughtful analysis, this one seemed like it was, "OMG that would be
totally cool". PEP 342 doesn't give any compelling use cases (it only
gives examples of "cool things you can do with coroutines"), no
discussion on how in improves the language. Suffice to say that,
thanks to this example, I'm more averse to using them than I was
before.


Carl Banks
From: Jérôme Mainka on
On 16 juin, 20:11, Carl Banks <pavlovevide...(a)gmail.com> wrote:

> I suggest, if you intend to use this kind of thing in real code (and I
> would not recommend that) that you get in a habit of explicitly
> closing the generator after the last send(), even when you don't think
> you have to.

Very clear explanation. Thanks for the investigation...


> IMHO, coroutines are the one time during the PEP-era that Python can
> be accused of feature creep.  All other changes seemed driven by
> thoughtful analysis, this one seemed like it was, "OMG that would be
> totally cool".  PEP 342 doesn't give any compelling use cases (it only
> gives examples of "cool things you can do with coroutines"), no
> discussion on how in improves the language.  Suffice to say that,
> thanks to this example, I'm more averse to using them than I was
> before.

The wonderful David Beazley's course [1] piqued my curiosity. But,
indeed, this is a hard piece difficult to master.


Thanks again,

Jérôme

[1] http://www.dabeaz.com/coroutines/