[3.12] Further improve docs for typing.Annotated (GH-105498) (#105503)

Further improve docs for `typing.Annotated` (GH-105498)
(cherry picked from commit d213c2990f)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Miss Islington (bot) 2023-06-08 08:12:35 -07:00 committed by GitHub
parent 2b6f475db8
commit 3c5f0eadd8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 81 additions and 57 deletions

View file

@ -1194,40 +1194,36 @@ These can be used as types in annotations using ``[]``, each having a unique syn
.. data:: Annotated .. data:: Annotated
A type, introduced in :pep:`593` (``Flexible function and variable Special typing form to add context-specific metadata to an annotation.
annotations``), to decorate existing types with context-specific metadata
(possibly multiple pieces of it, as ``Annotated`` is variadic).
Specifically, a type ``T`` can be annotated with metadata ``x`` via the
typehint ``Annotated[T, x]``. This metadata can be used for either static
analysis or at runtime: at runtime, it is stored in a :attr:`__metadata__`
attribute. If a library (or tool) encounters a typehint
``Annotated[T, x]`` and has no special logic for metadata ``x``, it
should ignore it and simply treat the type as ``T``. Unlike the
``no_type_check`` functionality that currently exists in the ``typing``
module which completely disables typechecking annotations on a function
or a class, the ``Annotated`` type allows for both static typechecking
of ``T`` (which can safely ignore ``x``)
together with runtime access to ``x`` within a specific application.
Ultimately, the responsibility of how to interpret the annotations (if Add metadata ``x`` to a given type ``T`` by using the annotation
at all) is the responsibility of the tool or library encountering the ``Annotated[T, x]``. Metadata added using ``Annotated`` can be used by
``Annotated`` type. A tool or library encountering an ``Annotated`` type static analysis tools or at runtime. At runtime, the metadata is stored
can scan through the annotations to determine if they are of interest in a :attr:`!__metadata__` attribute.
(e.g., using ``isinstance()``).
When a tool or a library does not support annotations or encounters an If a library or tool encounters an annotation ``Annotated[T, x]`` and has
unknown annotation it should just ignore it and treat annotated type as no special logic for the metadata, it should ignore the metadata and simply
the underlying type. treat the annotation as ``T``. As such, ``Annotated`` can be useful for code
that wants to use annotations for purposes outside Python's static typing
system.
It's up to the tool consuming the annotations to decide whether the Using ``Annotated[T, x]`` as an annotation still allows for static
client is allowed to have several annotations on one type and how to typechecking of ``T``, as type checkers will simply ignore the metadata ``x``.
merge those annotations. In this way, ``Annotated`` differs from the
:func:`@no_type_check <no_type_check>` decorator, which can also be used for
adding annotations outside the scope of the typing system, but
completely disables typechecking for a function or class.
Since the ``Annotated`` type allows you to put several annotations of The responsibility of how to interpret the metadata
the same (or different) type(s) on any node, the tools or libraries lies with the the tool or library encountering an
consuming those annotations are in charge of dealing with potential ``Annotated`` annotation. A tool or library encountering an ``Annotated`` type
duplicates. For example, if you are doing value range analysis you might can scan through the metadata elements to determine if they are of interest
allow this: (e.g., using :func:`isinstance`).
.. describe:: Annotated[<type>, <metadata>]
Here is an example of how you might use ``Annotated`` to add metadata to
type annotations if you were doing range analysis:
.. testcode:: .. testcode::
@ -1239,14 +1235,11 @@ These can be used as types in annotations using ``[]``, each having a unique syn
T1 = Annotated[int, ValueRange(-10, 5)] T1 = Annotated[int, ValueRange(-10, 5)]
T2 = Annotated[T1, ValueRange(-20, 3)] T2 = Annotated[T1, ValueRange(-20, 3)]
Passing ``include_extras=True`` to :func:`get_type_hints` lets one Details of the syntax:
access the extra annotations at runtime.
The details of the syntax:
* The first argument to ``Annotated`` must be a valid type * The first argument to ``Annotated`` must be a valid type
* Multiple type annotations are supported (``Annotated`` supports variadic * Multiple metadata elements can be supplied (``Annotated`` supports variadic
arguments):: arguments)::
@dataclass @dataclass
@ -1255,24 +1248,28 @@ These can be used as types in annotations using ``[]``, each having a unique syn
Annotated[int, ValueRange(3, 10), ctype("char")] Annotated[int, ValueRange(3, 10), ctype("char")]
* ``Annotated`` must be called with at least two arguments ( It is up to the tool consuming the annotations to decide whether the
client is allowed to add multiple metadata elements to one annotation and how to
merge those annotations.
* ``Annotated`` must be subscripted with at least two arguments (
``Annotated[int]`` is not valid) ``Annotated[int]`` is not valid)
* The order of the annotations is preserved and matters for equality * The order of the metadata elements is preserved and matters for equality
checks:: checks::
assert Annotated[int, ValueRange(3, 10), ctype("char")] != Annotated[ assert Annotated[int, ValueRange(3, 10), ctype("char")] != Annotated[
int, ctype("char"), ValueRange(3, 10) int, ctype("char"), ValueRange(3, 10)
] ]
* Nested ``Annotated`` types are flattened, with metadata ordered * Nested ``Annotated`` types are flattened. The order of the metadata elements
starting with the innermost annotation:: starts with the innermost annotation::
assert Annotated[Annotated[int, ValueRange(3, 10)], ctype("char")] == Annotated[ assert Annotated[Annotated[int, ValueRange(3, 10)], ctype("char")] == Annotated[
int, ValueRange(3, 10), ctype("char") int, ValueRange(3, 10), ctype("char")
] ]
* Duplicated annotations are not removed:: * Duplicated metadata elements are not removed::
assert Annotated[int, ValueRange(3, 10)] != Annotated[ assert Annotated[int, ValueRange(3, 10)] != Annotated[
int, ValueRange(3, 10), ValueRange(3, 10) int, ValueRange(3, 10), ValueRange(3, 10)
@ -1292,21 +1289,46 @@ These can be used as types in annotations using ``[]``, each having a unique syn
# ``Annotated[list[tuple[int, int]], MaxLen(10)]``: # ``Annotated[list[tuple[int, int]], MaxLen(10)]``:
type V = Vec[int] type V = Vec[int]
.. attribute:: __metadata__ * ``Annotated`` cannot be used with an unpacked :class:`TypeVarTuple`::
At runtime, the metadata associated with an ``Annotated`` type can be type Variadic[*Ts] = Annotated[*Ts, Ann1] # NOT valid
retrieved via the ``__metadata__`` attribute.
For example: This would be equivalent to::
.. doctest:: Annotated[T1, T2, T3, ..., Ann1]
>>> from typing import Annotated where ``T1``, ``T2``, etc. are :class:`TypeVars <TypeVar>`. This would be
>>> X = Annotated[int, "very", "important", "metadata"] invalid: only one type should be passed to Annotated.
>>> X
typing.Annotated[int, 'very', 'important', 'metadata'] * By default, :func:`get_type_hints` strips the metadata from annotations.
>>> X.__metadata__ Pass ``include_extras=True`` to have the metadata preserved:
('very', 'important', 'metadata')
.. doctest::
>>> from typing import Annotated, get_type_hints
>>> def func(x: Annotated[int, "metadata"]) -> None: pass
...
>>> get_type_hints(func)
{'x': <class 'int'>, 'return': <class 'NoneType'>}
>>> get_type_hints(func, include_extras=True)
{'x': typing.Annotated[int, 'metadata'], 'return': <class 'NoneType'>}
* At runtime, the metadata associated with an ``Annotated`` type can be
retrieved via the :attr:`!__metadata__` attribute:
.. doctest::
>>> from typing import Annotated
>>> X = Annotated[int, "very", "important", "metadata"]
>>> X
typing.Annotated[int, 'very', 'important', 'metadata']
>>> X.__metadata__
('very', 'important', 'metadata')
.. seealso::
:pep:`593` - Flexible function and variable annotations
The PEP introducing ``Annotated`` to the standard library.
.. versionadded:: 3.9 .. versionadded:: 3.9

View file

@ -2021,7 +2021,7 @@ class Annotated:
assert Annotated[int, '$'].__metadata__ == ('$',) assert Annotated[int, '$'].__metadata__ == ('$',)
- Nested Annotated are flattened:: - Nested Annotated types are flattened::
assert Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3] assert Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3]
@ -2032,15 +2032,17 @@ class Annotated:
- Annotated can be used as a generic type alias:: - Annotated can be used as a generic type alias::
Optimized = Annotated[T, runtime.Optimize()] type Optimized[T] = Annotated[T, runtime.Optimize()]
assert Optimized[int] == Annotated[int, runtime.Optimize()] # type checker will treat Optimized[int]
# as equivalent to Annotated[int, runtime.Optimize()]
OptimizedList = Annotated[List[T], runtime.Optimize()] type OptimizedList[T] = Annotated[list[T], runtime.Optimize()]
assert OptimizedList[int] == Annotated[List[int], runtime.Optimize()] # type checker will treat OptimizedList[int]
# as equivalent to Annotated[list[int], runtime.Optimize()]
- Annotated cannot be used with an unpacked TypeVarTuple:: - Annotated cannot be used with an unpacked TypeVarTuple::
Annotated[*Ts, Ann1] # NOT valid type Variadic[*Ts] = Annotated[*Ts, Ann1] # NOT valid
This would be equivalent to:: This would be equivalent to::