Code:
from typing import AsyncIterableimport asyncioasync def agen() -> AsyncIterable[str]:print('agen start')yield '1'yield '2'async def agenmaker() -> AsyncIterable[str]:print('agenmaker start')return agen()async def amain():print('amain')async for item in agen():passasync for item in await agenmaker():pass# Error:async for item in agenmaker():passdef main():asyncio.get_event_loop().run_until_complete(amain())if __name__ == '__main__':main()
As you can see, it is type-annotated, and contains an easy-to-miss error.
However, neither pylint
nor mypy
find that error.
Aside from unit tests, what options are there for catching such errors?
MyPy is perfectly capable of finding this issue. The problem is that unannotated functions are not inspected. Annotate the offending function as -> None
and it is correctly inspected and rejected.
# annotated with return type
async def amain() -> None:print('amain')async for item in agen():passasync for item in await agenmaker():pass# Error:async for item in agenmaker(): # error: "Coroutine[Any, Any, AsyncIterable[str]]" has no attribute "__aiter__" (not async iterable)pass
If you want to eliminate such issues slipping through, use the flags --disallow-untyped-defs
or --check-untyped-defs
.
MyPy: Function signatures and dynamic vs static typing
A function without type annotations is considered to be dynamically typed by mypy:
def greeting(name):return 'Hello ' + name
By default, mypy will not type check dynamically typed functions. This means that with a few exceptions, mypy will not report any errors with regular unannotated Python.