[Python-Dev] async/await in Python; v2

Greg Ewing greg.ewing at canterbury.ac.nz
Thu Apr 23 04:58:37 CEST 2015


On 04/23/2015 04:18 AM, Yury Selivanov wrote:

> 2. We'll hack Gen(/ceval.c?) objects to raise an error if they
> are called directly and have a 'CO_COROUTINE' flag.

By "Gen", do you mean the generator-function or the
generator-iterator?

That flag has to be on the generator-function, not the
generator-iterator, otherwise by the time ceval sees it,
the call that should have been forbidden has already
been made.

To make this work without flagging the function, it
would be necessary to check the result of every function
call that wasn't immediately awaited and raise an
exception if it were awaitable. But that would mean
awaitable objects not being fully first-class
citizens, since there would be some perfectly
reasonable things that you can't do with them.
I suspect it would make writing the kernel of a
coroutine-scheduling system such as asyncio very
awkward, perhaps impossible, to write in pure
Python.

> 3. Task(), create_task() and async() will be modified to call
> 'coro.__await__(..)' if 'coro' has a 'CO_COROUTINE' flag.

Or, as I pointed out earlier, the caller can wrap the
argument in something equivalent to costart().

> 4. 'await' will require parentheses grammatically. That will
> make it different from 'yield' expression. For instance,
> I still don't know what would 'await coro(123)()' mean.

In PEP 3152, cocall binds to the nearest set of function-calling
parens, so 'cocall f()()' is parsed as '(cocall f())()'.
If you want it the other way, you have to write it as
'cocall (f())()'.

I know that's a somewhat arbitrary thing to remember, and
it makes chained function calls a bit harder to write and
read. But chaining calls like that is a fairly rare thing
to do, in contrast with using a call expression as an
argument to another call, which is very common.

That's not the only case, either. Just about any unparenthesised
use of yield-from other than the sole contents of the RHS of
an assignment seems to be disallowed. All of these are
currently syntax errors, for example:

    yield from f(x) + yield from g(x)

    x + yield from g(x)

    [yield from f(x)]

> 5. 'await foo(*a, **k)' will be an equivalent to
> 'yield from type(coro).__await__(coro, *a, **k)'

Again, I'm not sure whether you're proposing to make the
functions the await-able objects rather than the iterators
(which would effectively be PEP 3152 with __cocall__
renamed to __await__) or something else. I won't comment
further on this point until that's clearer.

> 6. If we ever decide to implement coroutine-generators --
> async def functions with 'await' *and* some form of 'yield' --
> we'll need to reverse the rule -- allow __call__ and
> disallow __await__ on such objects (so that you'll be able
> to write 'async for item in coro_gen()' instead of
> 'async for item in await coro_gen()'.

Maybe. I haven't thought that idea through properly yet.
Possibly the answer is that you define such a function
using an ordinary "def", to match the way it's called.
The fact that it's an async generator is then indicated
by the fact that it contains "async yield".

-- 
Greg



More information about the Python-Dev mailing list