[Python-Dev] PEP 492 vs. PEP 3152, new round (original) (raw)

Łukasz Langa lukasz at langa.pl
Fri Apr 24 23:37:04 CEST 2015


On Apr 24, 2015, at 10:03 AM, Guido van Rossum <guido at python.org> wrote:

1. precise syntax of async def So I still prefer async def.

Me too. Also, we would use a similar vocabulary to existing users of the feature. This is exactly how Hack does it: http://docs.hhvm.com/manual/en/hack.async.php <http://docs.hhvm.com/manual/en/hack.async.php>, how ECMAScript 7 proposes it: http://wiki.ecmascript.org/doku.php?id=strawman:async_functions <http://wiki.ecmascript.org/doku.php?id=strawman:async_functions> and similarly to how C# does it (async comes after the public/private modifier but before the return type): https://msdn.microsoft.com/en-us/library/hh156513.aspx <https://msdn.microsoft.com/en-us/library/hh156513.aspx>

2. do we need async for and async with

Yes we do.

+1

(Though maybe we should consider await for and await with? That would have the advantage of making it easy to scan for all suspension points by searching for /await/. But being a verb it doesn't read very well.)

I’m on the fence here.

OT1H, I think “await for something in a_container” and “await with a_context_manager():” also read pretty well. It’s also more consistent with being the one way of finding “yield points”.

OTOH, “await with a_context_manager():” suggests the entire statement is awaited on, which is not true. “async with” is more opaque in this way, it simply states “there are going to be implementation-specific awaits inside”. So it’s more consistent with “async def foo()”.

All in all I think I’m leaning towards “async for” and “async with”. More importantly though, I’m wondering how obvious will the failure mode be when somebody uses a bare “for” instead of an “async for”. Ditto for “with” vs. “async with”. How much debugging will be necessary to find that it’s only a missing “async” before the loop?

Side note: to add to the confusion about syntax, Hack’s equivalent for “async for” - which doesn’t really translate well to Python - uses “await” and ties it to the iterable:

foreach ($list await as $input) { … }

The equivalent in Python would be:

for input in list await:

Notably, this is ugly and would be confused with for input in await list: which means something different.

Also, this particular construct represents less than 0.01% of all “await” occurences in Facebook code, suggesting it’s not performance critical.

3. syntactic priority of await

Yury, could you tweak the syntax for await so that we can write the most common usages without parentheses?

+1

Yury points out there was likely a reason this wasn’t the case for yield in the first place. It would be good to revisit that. Maybe for yield itself, too?

4. cocall vs. await

I just can't get used to this aspect of PEP 3152, so I'm rejecting it.

+1

(Yury: PEP 492 is not accepted yet, but you're getting closer.)

May I suggest using the bat-signal to summon Glyph to confirm this is going to be helpful/usable with Twisted as well?

5. do we really need _aiter_ and friends

There's a lot of added complexity, but I think it's worth it. I don't think we need to make the names longer, the 'a' prefix is fine for these methods.

+1

6. StopAsyncException

I'm not sure about this. The motivation given in the PEP seems to focus on the need for _anext_ to be async. But is this really the right pattern? What if we required ait._anext_() to return a future, which can either raise good old StopIteration or return the next value from the iteration when awaited? I'm wondering if there are a few alternatives to be explored around the async iterator protocol still.

So are you suggesting to pass the returned value in a future? In this case the future would need to be passed to anext, so the Cursor example from the PEP would look like this:

class Cursor: def init(self): self.buffer = collections.deque()

def _prefetch(self):
    ...

async def __aiter__(self):
    return self

async def __anext__(self, fut):
    if not self.buffer:
        self.buffer = await self._prefetch()
    if self.buffer:
        fut.set_result(self.buffer.popleft())
    else:
        fut.set_exception(StopIteration)

While this is elegant, my concern is that one-future-per-iteration-step might be bad for performance.

Maybe consider the following. The async def syntax decouples the concept of a coroutine from the implementation. While it’s still based on generators under the hood, the user no longer considers his “async function” to be a generator or conforming to the generator protocol. From the user’s perpective, it’s obvious that the return below means something different than the exception:

async def __anext__(self):
    if not self.buffer:
        self.buffer = await self._prefetch()
        if not self.buffer:
            raise StopIteration
    return self.buffer.popleft()

So, the same way we added wrapping in RuntimeErrors for generators in PEP 479, we might add transparent wrapping in a _StopAsyncIteration for CO_COROUTINE.

7. compatibility with asyncio and existing users of it

+1, this is really important.

-- Best regards, Łukasz Langa

WWW: http://lukasz.langa.pl/ Twitter: @llanga IRC: ambv on #python-dev

-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.python.org/pipermail/python-dev/attachments/20150424/00c46055/attachment.html>



More information about the Python-Dev mailing list