Whenever I'm not 100% sure what types a function accepts, I like to consult typeshed, which is the canonical repository of type hints for Python. Mypy directly bundles and uses typeshed to help it perform its typechecking, for example.
We can find the stubs for contextlib here: https://github.com/python/typeshed/blob/master/stdlib/contextlib.pyi
if sys.version_info >= (3, 2):
class GeneratorContextManager(ContextManager[_T], Generic[_T]):
def __call__(self, func: Callable[..., _T]) -> Callable[..., _T]: ...
def contextmanager(func: Callable[..., Iterator[_T]]) -> Callable[..., GeneratorContextManager[_T]]: ...
else:
def contextmanager(func: Callable[..., Iterator[_T]]) -> Callable[..., ContextManager[_T]]: ...
It's a little overwhelming, but the line we care about is this one:
def contextmanager(func: Callable[..., Iterator[_T]]) -> Callable[..., ContextManager[_T]]: ...
It states that the decorator takes in a Callable[..., Iterator[_T]]
-- a function with arbitrary arguments returning some iterator. So in conclusion, it would be fine to do:
@contextlib.contextmanager
def foo() -> Iterator[None]:
yield
So, why does using Generator[None, None, None]
also work, as suggested by the comments?
It's because Generator
is a subtype of Iterator
-- we can again check this for ourselves by consulting typeshed. So, if our function returns a generator, it's still compatible with what contextmanager
expects so mypy accepts it without an issue.