[Python-ideas] async/await in Python

Yury Selivanov yselivanov.ml at gmail.com
Tue Apr 21 01:59:09 CEST 2015


Hi Greg,

On 2015-04-20 7:32 PM, Greg Ewing wrote:
> Yury Selivanov wrote:
>> I added a section about PEP 3152 to PEP 492:
>> https://hg.python.org/peps/rev/428c7c753500
>>
>> Please take a look, and feel free to correct me if I
>> made any mistakes or forgot to specify something.
>
> From PEP 492:
>
>> 2. await is defined in almost the same way as yield from in the 
>> grammar (it is
>> later enforced that await can only be inside async def ). It is 
>> possible to
>> simply write await future , whereas cocall always requires parentheses. 
>
> The reason for this is to make cocalls grammatically similar
> to ordinary calls, so that a cocall can be used in any context
> where an ordinary call can be used. This is not the case with
> yield-from, which has to be enclosed in parentheses except it
> certain special contexts. If I understand your grammar correctly,
> "await" has the same characteristics as yield-from in this regard.

I'd say it's closer to 'yield'. So yes, you have to use parens
when writing 'print((await fut))', but since I use parens and
yield/yield-froms extremely rare myself and don't observe this
pattern to be common in other code too, I don't think it's an
issue.

I think that parens here is a nice compromise.  People already
know how to use yields, and when to put parens around them. I don't
want them to struggle with different grammar with 'await'.

>
>> 3. To make asyncio work with PEP 3152 it would be required to modify
>> @asyncio.coroutine decorator to wrap all functions in an object with a
>> __cocall__ method.
>
> That's not the only way to address this issue. I'll have to think
> about it further, but I don't think it would harm anything to have
> ordinary generator functions implement __cocall__. Then asyncio
> functions, and any other existing generator-based coroutines,
> could be used directly in cocalls without requiring any adaptation.
>
> Alternatively, a decorator equivalent to your async_def could be
> provided that turns an ordinary generator function into a cofunction.
> There would not be any additional overhead involved in calling such
> a function.

I think you're right here, we can implement __cocall__ on all
generators and let it work only for generators with 'CO_ASYNC'
flag (btw, I'm renaming it to 'CO_COROUTINE' for consistency).

The only problem is that if we apply this flag to coroutines
in asyncio, a lot of code can be broken.  I.e. whenever someone
passed around generator-objects, or used syntax like
'o = gen(); yield from o' there will be a *runtime* error.

Moreover, in the PEP 492 we define *awaitable* as a Future-like
or a *coroutine-object*.  We also require __a*__ methods
to return *awaitables*.  Hence you can implement patterns like
the following:

def __aenter__(self): // a function returning awaitable
    if something():
        return coroutine_func()
    else:
        return future()

or

async def __aenter__(self):  // a coroutine
     print('hi')

If we disallow calling coroutines (requiring
"await <..>()" syntax) this kind of patterns won't be
possible.

>
>> 4. Since it is impossible to call a cofunction without a cocall 
>> keyword, it
>> automatically prevents the common mistake of forgetting to use yield 
>> from on
>> generator-based coroutines. This proposal addresses this problem with a
>> different approach, see Debugging Features . 
>
> This is an important part of the philosophy behind PEP 3152. With
> my approach, there is no additional overhead involved in enforcing
> it, so there is no need to relegate it to a debugging feature, with
> the problems that you point out.
>

Agree.  I kind of like this design.  See my above comments though.

Thanks!
Yury



More information about the Python-ideas mailing list