Thank you for your thoughtful and informative reply Yury.

I'm mostly convinced that doc updates (to which you have already made
several) informing users of potential peril is the safest thing to do.  So
I won't harp on the issue going passed this reply.

With that being said, I am still a bit stuck on this belief
that CO_ITERABLE_COROUTINE and as you now pointed out
collections.abc.Coroutine types should be given the same treatment as
native coros where the question being asked is, "Can this be used like a
coroutine?"  From a high level perspective a user won't tend to care if the
underlying implementation of a coro is generator based when asking
questions about coro protocol.  They just want to know things like if they
need to call `await foo()` or `foo()`.  Put another way, it is an
implementation detail that some coros are generator based or otherwise (see
Cython collections.abc.Coroutine subtypes).

It's my belief that by increasing the breadth of a test like
inspect.iscoroutinefunction() you don't undermine the fact that some of
those functions are also generator functions, you merely complement it.
One can still ask if a coro is a generator if needed and by answering yes
to an object being a coroutine you aren't (or shouldn't be) altering its
generator protocol.  To me that would be the deciding factor for any calls
that might be extending their breadth in this regard.  For example, I ask
myself, what harm comes from changing the check in `_PyGen_Finalize` to
look for all types of coros, or just calling a more open minded
implementation of `inspect.iscoroutine`, as a determinant for that super
awesome "coroutine __ was never awaited" message?  As far as I can tell it
only increases consistency and determinism by reducing variance.

Again, thank you for taking the time to walk through this with me.  I'm a
huge asyncio fan and love building things with it.  Now it's time to get
back to doing just that...

Cheers,
Justin


On Tue, Nov 8, 2016 at 10:37 AM Yury Selivanov <yseliva...@gmail.com> wrote:

> Justin,
>
> First, thanks a lot for a detailed review of PEP 492/inspect/asyncio docs.
>
> > On Nov 8, 2016, at 4:39 AM, Justin Mayfield <too...@gmail.com> wrote:
> >
> > Reading into these flags more, I wonder if modifications to the
> inspect.iscoroutine* functions as well as to genobject.c's finalizer are
> justified.  Specifically to check for both CO_ITERABLE_COROUTINE and
> CO_COROUTINE in any place where a coroutine is expected.  Note that this is
> an inversion of my last email's statements about setting both flags in
> types.coroutine.  Here is some discovery to help in the discussion...
> >
> > The "New Coroutine Declaration Syntax" section of PEP 482 states,
> "Internally, two new code object flags were introduced: CO_COROUTINE is
> used to mark native coroutines (defined with new syntax).
> CO_ITERABLE_COROUTINE is used to make generator-based coroutines compatible
> with native coroutines (set by types.coroutine() function)." Along with,
> "When a coroutine is garbage collected, a RuntimeWarning is raised if it
> was never awaited on (see also Debugging Features ).”
>
> This is a bug in the PEP, I’ve pushed an update: https://git.io/vXuEQ
>
> >
> > The last comment does not delineate CO_ITERABLE_COROUTINE from
> CO_COROUTINE, which makes me think that the genobject.c finalizer code
> should check for un-awaited coroutines of both types.   There are a few
> other places affected by this including the repr output of objects as seen
> by before and after repr() calls to asyncio.sleep from my previous
> experiment in setting both COROUTINE flags...
> >
> > #stock  behavior..
> >    >>> repr(asyncio.sleep(1))
> >    '<generator object sleep at 0x7fa5ac7ee0a0>'
> >
> > # hacked behavior setting both CO_ITERABLE_COROUTINE and CO_COROUTINE...
> >    >>> asyncio.sleep(1)
> >    __main__:1: RuntimeWarning: coroutine 'sleep' was never awaited
> >    <coroutine object sleep at 0x7f8aac4c6360>
> >
> > I posit that this last behavior is desirable but should be accomplished
> by finding all places that check for CO_COROUTINE and have them check for
> CO_ITERABLE_COROUTINE too.
>
> I don’t think we should do that.  CO_COROUTINE denotes that the code
> object will return a new distinct type - PyCoro_Type (as defined in
> genobject.h).
>
> CO_ITERABLE_COROUTINE can’t exist on its own, it’s a modifier for
> CO_GENERATOR. Code objects with CO_ITERABLE_COROUTINE will still return
> *generators*, with only one extra feature: they can be awaited.  We decided
> that we don’t want to change generators semantics too much, after all we
> have CoroWrapper & debug mode in asyncio to catch unawaited coroutines.  We
> even use CoroWrapper for native coroutines, because it provides you with a
> more detailed stack trace.
>
> >
> > Lastly, the docstring from inspect.iscoroutinefunction says "Return true
> if the object is a coroutine function.  Coroutine functions are defined
> with "async def" syntax, or generators decorated with `types.coroutine`."
>  What I find problematic here is that types.coroutine never produces a
> function that is compatible with inspect.iscoroutinefunction.
>
> This was a bug in the docstring, updated now:
> https://hg.python.org/cpython/rev/f7f89a4e047d. The code was actually
> correct, inspect.iscoroutinefunction was checking for only CO_COROUTINE
> flag.
>
> >
> > My suggestion is that we use a mask of something like CO_COROUTINE_ANY
> defined as (CO_ITERABLE_COROUTINE & CO_COROUTINE), which can be used
> anywhere coroutine tests need to be performed.
>
> The flag won’t help you with Cython async-def coroutines.  They implement
> collections.abc.Coroutine but they don’t have code objects.
>
> I think I see your confusion now.  For determining if something is an
> asyncio compatible coroutine you should use asyncio.iscoroutine (which is
> compatible with all kinds of coroutines, including ones compiled with
> Cython). If you want to check if an object is a Python native type - use
> inspect.* functions.  This has to be better clarified in the docs, I agree.
>
> Yury

Reply via email to