The power of
Annotated is being unleashed
Annotated type arrived at Python 3.6 as per PEP 527. It is now getting more use cases.
What is it anyway?
mypy point of view,
Annotated[T, foo, bar, baz, ...] is strictly equivalent to
T, its first argument. The remaining arguments can be any Python objects. For instance,
greeting: Annotated[str, 5, ..., , 'boombadam', float]
Why to use it?
To attach additional metadata to type annotations in Python. Even though
mypy does not care for said metadata, other libraries might make use of it.
This table is a snapshot of things how they are at the moment of writing this post. Things are going to change.
|Library declaring the annotation||Annotation||Library using the annotation||Purpose|
||> < ⩾ ⩽ and generic interval validators|
||Sequence length validators|
||Validate timezone for a datetime value|
||Validate value satisfies a given predicate|
||Combine multiple annotations into one|
||Validate the value in strict mode|
||Specify Pydantic validation rules|
||mkdocstrings||Document a variable, function parameter, or class field|
||FastAPI||Designate this as a query parameter|
||Designate as a path parameter|
||Designate as a body parameter|
||Inject a dependency|
||Typer||Designate function parameter as CLI option or argument.|
Documenting function arguments
One of the cases above I would want to attract your attention to is
doc() annotation, proposed by PEP 727 and best illustrated by an example:
from typing import Annotated, doc def create_user( lastname: Annotated[str, doc("The **last name** of the newly created user")], firstname: Annotated[str | None, doc("The user's **first name**")] = None, ) -> Annotated[User, doc("The created user after saving in the database")]: """ Create a new user in the system, it needs the database connection to be already initialized. """
This is another way to document function parameters, in addition to two existing methods:
- in the docstring,
- and via inline comments.
Let's compare those approaches.
I am intentionally leaving out other applications of
doc() — annotation of variables and class fields. I believe that function signature annotations are much more often met in real code than the other use cases and thus will be most substantial to the overall developer experience.
🟡 Multiple competing standards
Google, numpy, and more
Or nothing I know of
by PEP 727
|Looking at parameter definition, how easy it is to locate its description?||
🟡 Moderately easy
One has to scroll to the docstring and find it there
|🟢 Very easy||🟢 Very easy|
|How verbose is function signature?||🟢 Not verbose||
🟡 A bit more verbose
🔴 Significantly more verbose
|How verbose is the docstring?||
Every parameter has to be detailed there
|🟢 Not verbose||🟢 Not verbose|
|How hard is it to extract parameter documentation programmatically?||
🔴 Very hard
Support multiple standards and recognize which is used
🟡 Moderately hard
Forces to extract docs from Python AST
🟢 Very easy
Verbosity of function signature is the most prevalently articulated argument against using
Annotated widely. However, it might be argued that verbosity does not appear out of the blue; it is just being moved from the docstring to the signature — and thus made arguably more manageable.
More ideas for annotations
If you have a hammer ⇒ everything around looks like a nail.
Annotated[dict, MutatedArgument('Accumulator to store cached `Badabazinga` instances.')]
Annotated[str, TODO('Use an `Enum` instead of a plain string.', ticket='PRJ-123')]
FIXMEmarkers more semantic and integrate them into project docs. IDE might show this hint every time the user hovers over the annotated value, even at a completely different place in the code.
Do you have anything in mind? Feel free to submit a PR.
I do not care for increased verbosity that the reliance on
Annotated introduces to type annotations for parameters, fields, and variables, as long as
- repetition is avoided,
- and the annotations provide both proteine- and silicon-based developers with semantically rich information about the code they are reading.
Looking forward to making my code more readable and documentable then it ever was.