Closed bj0 closed 8 years ago
Hi @bj0! Thanks for checking out my little library :-)
Why
YieldWrapper
?
(1) It's totally legal for an async for
to return awaitables. (2) While it happens to be true that asyncio
always uses awaitable objects to communicate between the coroutine runner and the leaf coroutines, this isn't mandatory -- other coroutine runners (tornado, curio, ...) can and do use non-awaitable objects for communication.
Defining my own sentinel value neatly avoids all these issues.
Why a new
ANextIter
for every__anext__
call?
You're right, that probably would be a little more efficient :-).
Why comment out
yield_from_
?
You're right, the commented out code does work fine. I commented it out at the request of @1st1 (= Yury Selivanov = the author of the async/await PEP), because we're talking about how to add a real async yield
feature to Python 3.6, and while it's easy to implement async yield from
in this library, it's much more complicated to implement inside the compiler/bytecode interpreter. Also, if we have to pick between asend
/athrow
and async yield from
, then it's not 100% clear which alternative is better (personally I actually have more of an immediate use case for athrow
than I do for async yield from
...). And it's easier to add features than to remove them...
Ahh OK I get it. I have no experience with the other coroutine runners, I was just trying to get it to also wrap normal generators, which isn't a big deal. I guess you could just wrap that generator in your own generator that wraps the result in your sentinel value.
Removed the unnecessary ANextIter
instantiations in 0e492ddfe544fc8fd512c458527cfde2efaac3a1, so closing this.
And yeah, in general you have to match async for
<-> async generator, for
<-> regular generator, just like you have to match await foo()
<-> async function, foo()
<-> regular function. But if you want to adapt a synchronous thing to an asynchronous thing then it's pretty trivial in both cases:
async def async_foo(*args, **kwargs):
return foo(*args, **kwargs)
@async_generator
async def async_my_gen(*args, **kwargs):
for obj in my_gen(*args, **kwargs):
await yield_(obj)
Thanks again for checking it out!
I saw your pycon lightning talk on youtube and thought it was pretty neat.
await yield
forasync for
has been something I've wanted since I first read aboutasync for
. I have made decorators that try to take care of all the__aiter__
/__anext__
boilerplate code in the past, but I have never seen one that used ayield_
function like yours (I have always used queues to pass values). It's a pretty clever idea.As I was reading through your code and testing differences between your implementation and mine, I had a few questions:
YieldWrapper
?It looks like you are using it as a sentinal value to determine when you've reached the end of the coroutine chain, but why not just yield the value directly and then in send() you can use
inspect.isawaitable(result)
to determine if you're at the end. I don't know if you would needinspect.isgenerator
orinspect.iscoroutine
, but for all the tests I've done,inspect.isawaitable(result)
has been sufficient.This would have the added benefit of allowing the decorator to turn any generator into an async iterable (not just ones using
yield_
).__anext__
call?It seems like unnecessary overhead to create a new instance for each
__anext__
call, why not just create an instance in__aiter__
(which is called once perasync for
) and give it a__anext__
that returnsself
? This also separates them more into an "async iterable" and "async iterator".yield_from_
?I read your README, and I agree that an
asend
orathrow
seem pretty weird, but I don't understand the problem withyield_from_
. The most obvious and useful case I can think of is generator delegation, and as far as I can tell your (commented out) version does that just fine.For reference, here's the version I was using (updated to adopt your
yield_
ideas and be more flexible): https://gist.github.com/bj0/0313ee67766de6a04881