Unfortunately my experience isn’t exactly recent - other than removing uses of them, I haven’t touched async generators for some years now. From memory, the issues I had were to do with exceptions and cancellations - but you can get other issues along those lines even if you’re not using async generators, so indeed the root cause may have been somewhere else, and merely avoided by switching away from async generators. It also doesn’t help that I’m still mainly using Python versions 3.10 and 3.11, so things may well have improved in newer versions. I realise now for these multiple reasons, I’m not really in a good position to argue here.
I am (broadly) aware of this distinction, though I was probably conflating some things. I think the asyncio module has some flaws, and separately I think the design of the async/await syntax has some flaws, and often these can overlap. (For clarity, I still believe they are extremely useful features - like I said, I love asyncio - I’m not bashing them.) Regardless, I understand that this and my understanding of how async generators work definitely isn’t on topic for this thread.
To try and explain my reasoning for why I think it’s counterintuitive for yield from to mean “yield from sync in async” and for async yield from to mean “yield from async in async”: really, it’s all down to the fact that you use the same yield x syntax in both sync and async generators.
Regular generator:
def sync_gen():
yield val
Async generator:
async def async_gen()
yield val
Then, when you want some form of composition:
def sync_gen():
yield from other_sync_gen()
By symmetry, the intuitive way to compose in an async generator would be:
async def async_gen():
yield from other_async_gen()
That said, while I have been interpreting the async in async yield from to be associated with the yield (which is why it didn’t make any sense), I can see the logic now, thanks to Alyssa’s explanation, that you have to consider it to be associated with the (invisible) for instead.
I still don’t find it intuitive, and so it still feels “wrong” to me - but I can at least now see why this pattern has been chosen.
Interesting; I’m glad you raise this point.
Are you suggesting that with async yield from added to the language, issues with delegating exception handling to subiterators might be solved, in a way that the existing async for x in y: yield x does not? In other words, that async yield from might not be equivalent to this, but superior to it?
If so, I believe async yield from might hold more value.
Finally, I’d like to apologise for derailing this thread - indeed, discussions of general problems with async isn’t on topic here, and admittedly my original post was a bit of a knee-jerk reaction when I saw the proposal, based on my bad experiences with async generators. (Usually posts like these end up in a text file on my PC, never to be seen again… this one got past the filter, I suppose.) I was quite surprised that the problems I’ve run into haven’t been encountered more widely. I’m now pretty convinced that I don’t really know what I’m talking about on this subject, so even though I still find the syntax weird, I’ll withdraw my -1.