Repository navigation
Allow types.GeneratorType to be used instead of collections.abc.Generator as the return type of generator functions #20522
Description
Feature
Currently, mypy only allows collections.abc.Generator and its supertypes as the return type of generator functions, and as a result, it cannot infer the correct return type for awaitable objects that define the __await__() method via types.GeneratorType:
import asyncio
from collections.abc import Generator
from types import GeneratorType
from typing import Never, reveal_type
class A:
def __await__(self) -> GeneratorType[Never, Never, bool]:
return True
yield # generator definition
class B:
def __await__(self) -> Generator[Never, Never, bool]:
return True
yield # generator definition
async def main() -> None:
reveal_type(await A()) # revealed type is "Any"
reveal_type(await B()) # revealed type is "builtins.bool"
reveal_type(A().__await__().gi_running) # revealed type is "builtins.bool"
reveal_type(B().__await__().gi_running) # has no attribute "gi_running"
if __name__ == "__main__":
asyncio.run(main())example.py:9: error: The return type of a generator function should be "Generator" or one of its supertypes [misc]
example.py:21: note: Revealed type is "Any"
example.py:22: note: Revealed type is "builtins.bool"
example.py:24: note: Revealed type is "builtins.bool"
example.py:25: error: "Generator[Never, Never, bool]" has no attribute "gi_running" [attr-defined]
example.py:25: note: Revealed type is "Any"
Found 2 errors in 1 file (checked 1 source file)
I propose allowing the use of types.GeneratorType with subsequent inference of the correct return type.
Pitch
First, pyright already has the expected behavior:
/home/user/workspace/example.py
/home/user/workspace/example.py:21:17 - information: Type of "await A()" is "bool"
/home/user/workspace/example.py:22:17 - information: Type of "await B()" is "bool"
/home/user/workspace/example.py:24:17 - information: Type of "A().__await__().gi_running" is "bool"
/home/user/workspace/example.py:25:17 - information: Type of "B().__await__().gi_running" is "Unknown"
/home/user/workspace/example.py:25:33 - error: Cannot access attribute "gi_running" for class "Generator[Never, Never, bool]"
Attribute "gi_running" is unknown (reportAttributeAccessIssue)
1 error, 0 warnings, 4 informations
Second, this relates to the eternally controversial python/typing#1480. In python/typeshed#10816, false properties related to native objects were removed in favor of the types module. You could say that we can use isinstance/inspect.isgenerator to narrow collections.abc.Generator down to types.GeneratorType, but... it is not that simple:
x = B().__await__()
reveal_type(x) # typing.Generator[Never, Never, builtins.bool]
if isgenerator(x):
reveal_type(x) # types.GeneratorType[Any, Any, Any]
if isinstance(x, GeneratorType):
reveal_type(x) # types.GeneratorType[Any, Any, Any]I think that allowing the use of types.GeneratorType would make it easier to inspect native generator objects. Especially since collections.abc.Generator can also be used to define custom generator types.
Related: #18635.
Activity
A5rocks commentedon Jan 4, 2026
Technically this wouldn't be completely correct, as I assume Cython doesn't produce
GeneratorType. (that's the case forCoroutineTypeat least).However, this would still be useful and I think is a good idea nonetheless.
x42005e1f commentedon Jan 4, 2026
The same applies to other types from the
typesmodule. For example, fortypes.FunctionType:A function compiled via Cython is not an instance of
types.FunctionType. Moreover, although a function compiled via Nuitka is, it does not allow the__code__attribute to be set at runtime (whereas in typeshed the attribute is defined as settable). However, at the same time, we do not even have any official ways to check that an object is "function-like" (there is even a private helper for this purpose; note that there is alsoinspect.isroutine(), but it is for a different purpose and does not cover all cases), and the same is true for all other types, including generator iterators.In light of the above, it seems to me that the
isinstance/inspect.is*checks are counterintuitive. They do not returnTruefor Cython objects (unless thetypesmodule itself is compiled, which is a very interesting scenario, by the way), but Cython objects have at least some attributes that their uncompiled counterparts have. One could even say that typeshed attempts to define the classes of thetypesmodule for only one of the possible runtime behaviors.So maybe possible false positives are not so bad after all?
x42005e1f commentedon Jan 4, 2026
I just checked for compliance with
types.GeneratorTypeaftercythonize -i -3(the latest Cython):coroutines
async generators (buggy?)
x42005e1f commentedon Jan 5, 2026
Looking at it from another angle... it is even more ambiguous.
Les us start with the fact that typing itself is not platform-independent. For example, the following code will not pass type-checking on any platform when both lines are included:
But what should be considered the current implementation of Python? Well, obviously,
types.pyi(typeshed) is defined for CPython. Moreover, it is not compatible with PyPy:Cython is not generally considered a separate Python implementation, but as in my previous comment, it changes the behavior of compiled code, making it incompatible with CPython-level execution. Should it even be considered from a typing perspective if the current state of typing is not suitable for anything other than CPython on the current platform?
refactor!: avoid native types