Comments inline..

>From types.coroutine (which is also called by asyncio.coroutine)...
211 def coroutine(func):
212     """Convert regular generator function to a coroutine."""
213
214     if not callable(func):
215         raise TypeError('types.coroutine() expects a callable')
216
217     if (func.__class__ is FunctionType and
218         getattr(func, '__code__', None).__class__ is CodeType):
219
220         co_flags = func.__code__.co_flags
221
222         # Check if 'func' is a coroutine function.
223         # (0x180 == CO_COROUTINE | CO_ITERABLE_COROUTINE)
224         if co_flags & 0x180:
225             return func
226
227         # Check if 'func' is a generator function.
228         # (0x20 == CO_GENERATOR)
229         if co_flags & 0x20:
230             # TODO: Implement this in C.
231             co = func.__code__
232             func.__code__ = CodeType(
233                 co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
234                 co.co_stacksize,
*235                 co.co_flags | 0x100,  # 0x100 == CO_ITERABLE_COROUTINE*
236                 co.co_code,
237                 co.co_consts, co.co_names, co.co_varnames,
co.co_filename,
238                 co.co_name, co.co_firstlineno, co.co_lnotab,
co.co_freevars,
239                 co.co_cellvars)
240             return func

Line 235: Setting CO_ITERABLE_COROUTINE but not CO_ITERABLE makes
inspect.awaitable(corofunc()) pass but prevents
inspect.iscoroutinefunction(forofunc) from passing.  Setting the flags to
(CO_ITERABLE_COROUTINE & CO_ITERABLE) in this same context actually
produces passing behavior for inspect.iscoroutinefuncion.

In addition, it triggers some other diagnostic code from genobject so you
get some handy messages like, "RuntimeWarning: coroutine 'sleep' was never
awaited".

See (from genobject.c):
  62     /* If `gen` is a coroutine, and if it was never awaited on,
  63        issue a RuntimeWarning. */
  64     if (gen->gi_code != NULL &&
  65         ((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
  66         gen->gi_frame->f_lasti == -1) {
  67         if (!error_value) {
  68             PyErr_WarnFormat(PyExc_RuntimeWarning, 1,
  69                              "coroutine '%.50S' was never awaited",
  70                              gen->gi_qualname);
  71         }
  72     }

My primary question here is about the intended difference
between CO_ITERABLE_COROUTINE and CO_ITERABLE;  How perverse is a change
that naively modifies line 235 of types.coroutine to set both flags?  In my
limited testing it produced better behavior than stock, but I honestly
can't figure out the intended differences between those flags.

On Mon, Nov 7, 2016 at 10:58 PM Yury Selivanov <[email protected]>
wrote:

> On 2016-11-08 12:47 AM, Justin Mayfield wrote:
>
> > That's quite surprising to me too, BTW.  I was focused on generator vs
> > `async def ...` coros and hadn't even tried the legacy decorator.
> >
> > Would it be possible for asyncio code to be altered in a way that
> produces
> > awaitables which conform with inspect's protocol?   Ie. through code
> flags
> > or some other flag that inspect looks for?  I assume a similar stance
> could
> > then be taken by other async libraries.
> There is no protocol here. The inspect module is simply tailored for
> introspection of builtin types. If something mimics a builtin type (a
> coroutine, a generator, or a simple function) inspect utils won't
> recognize it.
>
> > If it's just not in the cards may I humbly suggest the docs for the
> inspect
> > module include some language to help users avoid some of these
> > idiosyncrasies?
>
> Yes, I think we can improve docs: better clarify what is a
> coroutine/coroutine function, reference asyncio functions, add an
> example.  Feel free to submit a patch!
>
> Yury
>
>

Reply via email to