When you use type hints/annotations in Python, you could be using them for one or more of at least 5 different things:
Interactive programming help
Many editors will be able to use type hints to give you help with:
autocomplete (e.g. suggesting methods that actually exist on the type of objects you are dealing with)
immediate error checking (e.g. squiggly red lines under mistakes)
code navigation (e.g. jump to definition)
refactoring (e.g. renaming a method and all uses of it)
To be clear, these features can often work without type hints – for example, I’ve used jedi very effectively to provide jump to definition etc. on code bases without any type hints, and many linters like flake8 and ruff also provide a lot of functionality without types. But type hints can help a lot in cases where static analysis wouldn’t otherwise give clear answers.
Static type checking
This is where a tool uses the type annotations to check the correctness of your code. I’m distinguishing this from the “immediate error checking” use case above, even though the same tool such as mypy or pyright might be behind it, because I’m specifically thinking of cases where your code will be rejected by something in your process (like checks in your CI build system) if type checking reports errors. This case is different because help in your editor can be ignored if it is wrong, but static type checks built into your deployment processes etc. either cannot be skipped, or require extra work to ignore. “Friendly assistant” and “opinionated gatekeeper” are quite different personas, and you might not appreciate them equally.
Runtime behaviour determination
Running Python code can use reflection/introspection techniques to inspect type hints and change behaviour on that basis. The most obvious example in my mind is pydantic, which uses type annotations to determine what correct inputs look like, and also serialisation/deserialisation behaviour. Another example would be runtime type checking like beartype or typeguard.
UPDATE: Another use is dependency injection (Lagom) (thanks antoinewdg), and other notable projects leaning on runtime use of type hints include FastAPI and Typer (thanks ubernostrum). There are probably lots more.
Code documentation
A type hint can be used to tell a user what kind of objects a function accepts or produces. This can be extracted in automatically created docs, or shown in your editor (in which case it also falls under “Interactive programming help”).
An interesting application of this is drf-spectacular, which uses type hints as well as other information to extract an OpenAPI spec from a project using DRF. This spec, as well as serving as documentation or input to tools like redoc or Swagger UI, can also be used for type checking or code generation in another language, typically for web frontend code, via tools like OpenAPI generator.
Compiler instructions
I don’t know how many people are doing this, but tools like mypyc will use type hints to compile Python code to something faster, like C extensions. Using type annotations to speed up Python is quite hard in practice.
Conclusion
There isn’t really a point of this post other than to say “be aware of these different use cases”. This awareness can be very important when you are in any discussion about the usefulness or necessity of type hints – which scenarios are you thinking about?
Also, when you are weighing up whether to add type hints, you might decide to do so in order to support some of these but not others – as I did for the parsy library.
Finally, and this is a bit of a gotcha to leave you with, you may need to be very aware of the different use cases when thinking about correctness. If you see count: int
, what kind of guarantee do you have that the count
name is actually bound to an int
object at runtime? Is the type hint invoking some runtime checking, or is it merely docs, or hoping for a static type check that might not actually happen? You probably need to know which it is!