Async/Await¶
Similar to the yield statement and generators, every function with an async
prefix is a coroutine function. Calling the coroutine function doesn’t execute
the code directly instead it returns a coroutine object.
 async def foo():
    pass
print(type(foo))
coro = foo()
print(type(coro))
print(dir(coro))
Output:
>>> print(type(foo))
<class 'function'>
>>> coro = foo()
>>> print(type(coro))
<class 'coroutine'>
>>> print(dir(coro))
['__await__', ...]
>>> "__await__" in dir(f)
True
>>> "__iter__" in dir(f)
False
These coroutine objects implement the Awaitable protocol but the aren’t
Iterables.
class Coroutine(Awaitable):
    def send(self, value):
        """Send a value into the coroutine.
        Return next yielded value or raise StopIteration.
        """
    def throw(self, typ, val=None, tb=None):
        """Raise an exception in the coroutine.
        Return next yielded value or raise StopIteration.
        """
    def close(self):
        """Raise GeneratorExit inside coroutine.
        """
        try:
            self.throw(GeneratorExit)
        except (GeneratorExit, StopIteration):
            pass
        else:
            raise RuntimeError("coroutine ignored GeneratorExit")
Besides the async def function definition there are
additional new expressions and statements. await
for awaiting a coroutine result, async for
for iterating over async iterables and async with
for async context managers.
awaitexpressions,async forandasync withcan only be used in the body of a coroutine function.Functions defined with async def syntax are always coroutine functions, even if they do not contain await or async keywords.
Summary
Coroutinesare now explicit objects and notGeneratorsanymore despite sharing a very very similar interface.CoroutinesareAwaitablesbut also notIterables.If
awaitis used in a function it must be declared as async and therefore becomes a native coroutine function.