You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
$ mypy file.py
file.py:5: error: Revealed type is 'def () -> typing.Awaitable[typing.AsyncGenerator[builtins.str, builtins.None]]'
file.py:9: error: Revealed type is 'def () -> typing.AsyncGenerator[builtins.str, builtins.None]'
So the type of the first one gets wrapped in Awaitable. I don't fully understand why. I thought all Async... are Awaitables by definition. I did not find anything about it but here #3576, which is not enough for me to get it.
file.py:4: error: Revealed type is 'def (self: acm.workers.BaseClass) -> typing.Awaitable[typing.AsyncGenerator[builtins.str, builtins.None]]'
file.py:7: error: Return type of "gen1" incompatible with supertype "BaseClass"
file.py:9: error: Revealed type is 'def (self: acm.workers.MyClass) -> typing.AsyncGenerator[builtins.str, builtins.None]'
So now I cannot define abstract methods that are async generators, in a straightforward way.
Versions
Python 3.6.5
mypy==0.600
EDIT: I fixed a random typo @JelleZijlstra found and edited parts of the text based on the typo. Now the whole problem is clearer.
Your gen1 is not a generator (it has no yield), so mypy interprets it as a normal async function that returns an AsyncGenerator object. gen2 is actually correctly typed as def () -> typing.AsyncGenerator[builtins.str, builtins.None], but your second reveal_type has a typo.
The issue with overrides can be explained by the same logic: the base class's gen1 is not a generator because there's no yield. You can probably work around it by adding a dummy yield inside the function or by removing the async from its def (I haven't tested), but it does seem like a usability issue that the obvious way to type an abstract AsyncGenerator doesn't work.
The if False: yield trick is as old as Python probably is. I think the source of confusion here may be that type annotations are completely ignored by Python runtime. Just by typing something as a generator it will not magically become a generator, you still need a yield somewhere.
There is unfortunately no simple solution, we may just document this in common issues.
Reacted by yxliang, andreymal, Eugene, Ofer (pitz) Horowitz, Povilas Balciunas, Oskar Sharipov, Tomas Votava, Vlada Dusek, Martin Ueding and Dali Chelbi
Thank you both, now I get it. My typo (which I will correct in a second) only added to my confusion. I agree that this probably should be explained in docs somewhere.
Update: I started to use plain yield in those kind of abstract async generators and I can say I like it: it's just as succinct as pass (which is, in this context, equivalent to plain return) and informative for the user as well.
test.py:34: error: Return type "AsyncIterator[int]" of "run" incompatible with return type "Coroutine[Any, Any, AsyncIterator[int]]" in supertype "Base" [override]
Activity
JelleZijlstra commentedon May 17, 2018
Your
gen1is not a generator (it has noyield), so mypy interprets it as a normal async function that returns an AsyncGenerator object.gen2is actually correctly typed asdef () -> typing.AsyncGenerator[builtins.str, builtins.None], but your secondreveal_typehas a typo.The issue with overrides can be explained by the same logic: the base class's
gen1is not a generator because there's noyield. You can probably work around it by adding a dummyyieldinside the function or by removing theasyncfrom its def (I haven't tested), but it does seem like a usability issue that the obvious way to type an abstract AsyncGenerator doesn't work.ilevkivskyi commentedon May 17, 2018
The
if False: yieldtrick is as old as Python probably is. I think the source of confusion here may be that type annotations are completely ignored by Python runtime. Just by typing something as a generator it will not magically become a generator, you still need ayieldsomewhere.There is unfortunately no simple solution, we may just document this in common issues.
pawelswiecki commentedon May 17, 2018
Thank you both, now I get it. My typo (which I will correct in a second) only added to my confusion. I agree that this probably should be explained in docs somewhere.
pawelswiecki commentedon May 18, 2018
Update: I started to use plain
yieldin those kind of abstract async generators and I can say I like it: it's just as succinct aspass(which is, in this context, equivalent to plainreturn) and informative for the user as well.Async functions return typing.Coroutine
hlovatt commentedon May 4, 2020
I too like the plain
yieldbut think it needs to be documented, it took a bit of googling to find this page!ashb commentedon Feb 25, 2022
A slightly different case to this (that is still fixed by doing
if False: yield) is if your parent ABC class:This will fail with
By making it a generator it passes
Even if the
raise NotImplementedErrorisn't in the base function body the error remains the same. Should this ABC case be handed specially?pamelafox commentedon Aug 15, 2023
Sharing a similar example as the previous comment:
approach.py:
chatreadretrieveread.py:
results in:
The "if False: yield" trick does work to remove the mypy errors.
hauntsaninja commentedon Aug 15, 2023
This has been documented at https://mypy.readthedocs.io/en/stable/more_types.html#asynchronous-iterators for a couple months now
ikonst commentedon Aug 16, 2023
Took a stab at adding contextual help in #15883.
(... Although, this wouldn't help #5070 (comment) since it's a difference in more than just the return type...)
Add hint for AsyncIterator incompatible return type (#15883)