Why python3.6 could yield from a coroutine object in a non-coroutine generator?

Viewed 205

zz.py

import asyncio

# @asyncio.coroutine
def main():
    yield from asyncio.sleep(1)

next(main())
  • Execute in Python 3.6.7 --> OK, no error.

  • Execute in Python 3.7.3 --> NOT OK, reported next:

      zz.py:5: RuntimeWarning: coroutine 'sleep' was never awaited
        yield from asyncio.sleep(1)
      RuntimeWarning: Enable tracemalloc to get the object allocation traceback
      Traceback (most recent call last):
        File "zz.py", line 7, in <module>
          next(main())
        File "zz.py", line 5, in main
          yield from asyncio.sleep(1)
      TypeError: cannot 'yield from' a coroutine object in a non-coroutine generator
    

I know I could enable @asyncio.coroutine to make both work, or async/await.

But, here my question is: if I still use generator, what's the difference here for python3.6 & python3.7? I really feel chaos here.

1 Answers

I found the root cause after dig into the python source code:

  • In python3.6, although async/await already there, coroutine.sleep still use generator based coroutine:

    @coroutine
    def sleep(delay, result=None, *, loop=None):
        """Coroutine that completes after a given time (in seconds)."""
        if delay == 0:
            yield
            return result
    
        if loop is None:
            loop = events.get_event_loop()
        future = loop.create_future()
        h = future._loop.call_later(delay,
                                    futures._set_result_unless_cancelled,
                                    future, result)
        try:
            return (yield from future)
        finally:
            h.cancel()
    
  • In python3.7, coroutine.sleep starts to switch to native coroutine:

    async def sleep(delay, result=None, *, loop=None):
        """Coroutine that completes after a given time (in seconds)."""
        if delay <= 0:
            await __sleep0()
            return result
    
        if loop is None:
            loop = events.get_event_loop()
        future = loop.create_future()
        h = loop.call_later(delay,
                            futures._set_result_unless_cancelled,
                            future, result)
        try:
            return await future
        finally:
            h.cancel()
    

So, when python3.6 execute yield from asyncio.sleep(1), it in fact did not yield from a native coroutine, surely not have error.

Related