First of all, we should ping Yury, who implemented `async for` about 6 years ago (see PEP 492), and Joshua Bronson, who implemented aiter() and anext() about 5 months ago (see
https://bugs.python.org/issue31861). I've CC'ed them here.
My own view:
A. iter() doesn't check that the thing returned implements __next__, because it's not needed -- iterators having an __iter__ methor is a convention, not a requirement. You shouldn't implement __iter__ returning something that doesn't implement __iter__ itself, because then "for x in iter(a)" would fail even though "for x in a" works. But you get an error, and anyone who implements something like that (or uses it) deserves what they get. People know about this convention and the ABC enforces it, so in practice it will be very rare that someone gets bitten by this.
B. aiter() shouldn't need to check either, for exactly the same reason. I *suspect* (but do not know) that the extra check for the presence of __iter__ is simply an attempt by the implementer to enforce the convention. There is no *need* other than ensuring that "async for x in aiter(a)" works when "async for x in a" works.
Note that PEP 525, which defines async generators, seems to imply that an __aiter__ returning self is always necessary, but I don't think it gives a reason.
FWIW I don't think there are any optimizations that avoid calling __iter__ or __aiter__ if __next__ or __anext__ is present. And certainly I wouldn't endorse adding them (this would seem an ad-hoc optimization that could break user expectations unexpectedly, quite apart from the issue discussed here).
--Guido