The intention of this code is to make it possible (and legal) to decorate a
non-generator function with `@asyncio.coroutine`, and to make that
effectively equivalent to the same decorated function with an unreachable
yield inside it (which makes it a generator). IOW these two should be
equivalent:

@asyncio.coroutine
def foo():
    return 42

@asyncio.coroutine
def foo():
    return 42
    yield  # dummy to make it a coroutine

You should be able to write in some other coroutine

    x = yield from foo()
    assert x == 42

regardless of which definition of foo() you've got.

Note that for the first version the decorator always matters, while for the
second version it only matters in debug mode.

But it looks like the code has some subtle bug. :-(

I think one of the problems is that we don't always test with debug mode.

There are probably also some cases that the code is trying to support, e.g.
for generators in general (outside the context of asyncio) there is usually
no difference between these two examples:

def foo():
    yield 1
    yield 2
def bar():
    return foo()

vs.

def bar():
    yield 1
    yield 2

-- in either case, calling bar() returns a generator that when iterated
over generates the sequence [1, 2]. But what you've discovered is that if
we decorate both functions with @asyncio.coroutine, there *is* a
difference. And I think the code is making a half-hearted attempt to paper
over the difference, but failing due to debug mode.

On Mon, Nov 9, 2015 at 10:25 AM, Vladimir Rutsky <rutsky.vladi...@gmail.com>
wrote:

> Hello!
>
> Can anybody explain to me this part of asyncio.coroutine code:
> <
> https://github.com/python/asyncio/blob/3b6a64a9fb6ec4ad0c984532aa776b130067c901/asyncio/coroutines.py#L201
> >
>
> # asyncio/coroutines.py:201:
> def coroutine(func):
>     """Decorator to mark coroutines.
>     If the coroutine is not yielded from before it is destroyed,
>     an error message is logged.
>     """
>     if _inspect_iscoroutinefunction(func):
>         # In Python 3.5 that's all we need to do for coroutines
>         # defiend with "async def".
>         # Wrapping in CoroWrapper will happen via
>         # 'sys.set_coroutine_wrapper' function.
>         return func
>
>     if inspect.isgeneratorfunction(func):
>         coro = func
>     else:
>         @functools.wraps(func)
>         def coro(*args, **kw):
>             res = func(*args, **kw)
>             if isinstance(res, futures.Future) or
> inspect.isgenerator(res): # <--- This part
>                 res = yield from res
>             ...
>
> What does this code: if wrapped by asyncio.coroutine function is not
> generator and returns generator, Future or awaitable object,
> then this coroutine function will yield from returned value.
>
> E.g. following coroutine function will actually return result of the
> future, not future itself:
>
> @asyncio.coroutine
> def return_future():
>     fut = asyncio.Future()
>     fut.set_result("return_future")
>
>     return fut
>
> I can't find where such behaviour is documented? Is this a desired
> behavior?
>
> According to <
> https://docs.python.org/3/library/asyncio-task.html?highlight=iscoroutine#coroutines
> >:
> > Coroutines used with asyncio may be implemented using the async def
> statement, or by using generators.
> > ...
> > Generator-based coroutines should be decorated with @asyncio.coroutine,
> although this is not strictly enforced.
>
> So technically, according to this part of documentation, it's not
> explicitly allowed to use asyncio.coroutine with non-generator functions.
>
> Special behaviour when asyncio.coroutine-wrapped non-generator function
> returns generator or Future object
> leads to inconsistent behaviour on Python 3.4 when asyncio debugging is
> enabled and not enabled.
>
> Consider following function:
>
> @asyncio.coroutine
> def return_coroutine_object():
>     @asyncio.coroutine
>     def g():
>         yield from asyncio.sleep(0.01)
>         return "return_coroutine_object"
>
>     return g()
>
> When debugging is disabled following code will work:
>
> assert loop.run_until_complete(return_coroutine_object()) ==
> "return_coroutine_object"
>
> When debugging is enabled same code will not work on Python 3.4,
> because result of "g()" is not a generator or Future --- it's debugging
> CoroWrapper.
> Outer asyncio.coroutine will not yield from CoroWrapper and will return
> inner "g()" CoroWrapper.
>
> I made complete example demonstrating this issue:
> <https://gist.github.com/rutsky/c72be2edeb1c8256d680>
>
> This issue is not reproduced in Python 3.5, since CoroWrapper is
> awaitable in Python 3.5,
> but still I can't find is this is a desired behaviour and why?
>
>
> Regards,
>
> Vladimir Rutsky
>
>


-- 
--Guido van Rossum (python.org/~guido)

Reply via email to