bpo-39491: Merge PEP 593 (typing.Annotated) support (#18260)
* bpo-39491: Merge PEP 593 (typing.Annotated) support PEP 593 has been accepted some time ago. I got a green light for merging this from Till, so I went ahead and combined the code contributed to typing_extensions[1] and the documentation from the PEP 593 text[2]. My changes were limited to: * removing code designed for typing_extensions to run on older Python versions * removing some irrelevant parts of the PEP text when copying it over as documentation and otherwise changing few small bits to better serve the purpose * changing the get_type_hints signature to match reality (parameter names) I wasn't entirely sure how to go about crediting the authors but I used my best judgment, let me know if something needs changing in this regard. [1]8280de241f/typing_extensions/src_py3/typing_extensions.py
[2]17710b8798/pep-0593.rst
This commit is contained in:
parent
89ae20b30e
commit
cf5b109dbb
|
@ -1028,7 +1028,7 @@ The module defines the following classes, functions and decorators:
|
||||||
runtime we intentionally don't check anything (we want this
|
runtime we intentionally don't check anything (we want this
|
||||||
to be as fast as possible).
|
to be as fast as possible).
|
||||||
|
|
||||||
.. function:: get_type_hints(obj[, globals[, locals]])
|
.. function:: get_type_hints(obj, globalns=None, localns=None, include_extras=False)
|
||||||
|
|
||||||
Return a dictionary containing type hints for a function, method, module
|
Return a dictionary containing type hints for a function, method, module
|
||||||
or class object.
|
or class object.
|
||||||
|
@ -1041,6 +1041,22 @@ The module defines the following classes, functions and decorators:
|
||||||
a dictionary constructed by merging all the ``__annotations__`` along
|
a dictionary constructed by merging all the ``__annotations__`` along
|
||||||
``C.__mro__`` in reverse order.
|
``C.__mro__`` in reverse order.
|
||||||
|
|
||||||
|
The function recursively replaces all ``Annotated[T, ...]`` with ``T``,
|
||||||
|
unless ``include_extras`` is set to ``True`` (see :class:`Annotated` for
|
||||||
|
more information). For example::
|
||||||
|
|
||||||
|
class Student(NamedTuple):
|
||||||
|
name: Annotated[str, 'some marker']
|
||||||
|
|
||||||
|
get_type_hints(Student) == {'name': str}
|
||||||
|
get_type_hints(Student, include_extras=False) == {'name': str}
|
||||||
|
get_type_hints(Student, include_extras=True) == {
|
||||||
|
'name': Annotated[str, 'some marker']
|
||||||
|
}
|
||||||
|
|
||||||
|
.. versionchanged:: 3.9
|
||||||
|
Added ``include_extras`` parameter as part of :pep:`593`.
|
||||||
|
|
||||||
.. function:: get_origin(tp)
|
.. function:: get_origin(tp)
|
||||||
.. function:: get_args(tp)
|
.. function:: get_args(tp)
|
||||||
|
|
||||||
|
@ -1372,3 +1388,87 @@ The module defines the following classes, functions and decorators:
|
||||||
evaluated, so the second annotation does not need to be enclosed in quotes.
|
evaluated, so the second annotation does not need to be enclosed in quotes.
|
||||||
|
|
||||||
.. versionadded:: 3.5.2
|
.. versionadded:: 3.5.2
|
||||||
|
|
||||||
|
.. data:: Annotated
|
||||||
|
|
||||||
|
A type, introduced in :pep:`593` (``Flexible function and variable
|
||||||
|
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. 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`` (e.g., via mypy or Pyre, 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
|
||||||
|
at all) is the responsibility of the tool or library encountering the
|
||||||
|
``Annotated`` type. A tool or library encountering an ``Annotated`` type
|
||||||
|
can scan through the annotations to determine if they are of interest
|
||||||
|
(e.g., using ``isinstance()``).
|
||||||
|
|
||||||
|
When a tool or a library does not support annotations or encounters an
|
||||||
|
unknown annotation it should just ignore it and treat annotated type as
|
||||||
|
the underlying type.
|
||||||
|
|
||||||
|
It's up to the tool consuming the annotations to decide whether the
|
||||||
|
client is allowed to have several annotations on one type and how to
|
||||||
|
merge those annotations.
|
||||||
|
|
||||||
|
Since the ``Annotated`` type allows you to put several annotations of
|
||||||
|
the same (or different) type(s) on any node, the tools or libraries
|
||||||
|
consuming those annotations are in charge of dealing with potential
|
||||||
|
duplicates. For example, if you are doing value range analysis you might
|
||||||
|
allow this::
|
||||||
|
|
||||||
|
T1 = Annotated[int, ValueRange(-10, 5)]
|
||||||
|
T2 = Annotated[T1, ValueRange(-20, 3)]
|
||||||
|
|
||||||
|
Passing ``include_extras=True`` to :func:`get_type_hints` lets one
|
||||||
|
access the extra annotations at runtime.
|
||||||
|
|
||||||
|
The details of the syntax:
|
||||||
|
|
||||||
|
* The first argument to ``Annotated`` must be a valid type
|
||||||
|
|
||||||
|
* Multiple type annotations are supported (``Annotated`` supports variadic
|
||||||
|
arguments)::
|
||||||
|
|
||||||
|
Annotated[int, ValueRange(3, 10), ctype("char")]
|
||||||
|
|
||||||
|
* ``Annotated`` must be called with at least two arguments (
|
||||||
|
``Annotated[int]`` is not valid)
|
||||||
|
|
||||||
|
* The order of the annotations is preserved and matters for equality
|
||||||
|
checks::
|
||||||
|
|
||||||
|
Annotated[int, ValueRange(3, 10), ctype("char")] != Annotated[
|
||||||
|
int, ctype("char"), ValueRange(3, 10)
|
||||||
|
]
|
||||||
|
|
||||||
|
* Nested ``Annotated`` types are flattened, with metadata ordered
|
||||||
|
starting with the innermost annotation::
|
||||||
|
|
||||||
|
Annotated[Annotated[int, ValueRange(3, 10)], ctype("char")] == Annotated[
|
||||||
|
int, ValueRange(3, 10), ctype("char")
|
||||||
|
]
|
||||||
|
|
||||||
|
* Duplicated annotations are not removed::
|
||||||
|
|
||||||
|
Annotated[int, ValueRange(3, 10)] != Annotated[
|
||||||
|
int, ValueRange(3, 10), ValueRange(3, 10)
|
||||||
|
]
|
||||||
|
|
||||||
|
* ``Annotated`` can be used with nested and generic aliases::
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
Vec = Annotated[List[Tuple[T, T]], MaxLen(10)]
|
||||||
|
V = Vec[int]
|
||||||
|
|
||||||
|
V == Annotated[List[Tuple[int, int]], MaxLen(10)]
|
||||||
|
|
||||||
|
.. versionadded:: 3.9
|
||||||
|
|
|
@ -303,6 +303,14 @@ signal
|
||||||
Exposed the Linux-specific :func:`signal.pidfd_send_signal` for sending to
|
Exposed the Linux-specific :func:`signal.pidfd_send_signal` for sending to
|
||||||
signals to a process using a file descriptor instead of a pid. (:issue:`38712`)
|
signals to a process using a file descriptor instead of a pid. (:issue:`38712`)
|
||||||
|
|
||||||
|
typing
|
||||||
|
------
|
||||||
|
|
||||||
|
:pep:`593` introduced an :data:`typing.Annotated` type to decorate existing
|
||||||
|
types with context-specific metadata and new ``include_extras`` parameter to
|
||||||
|
:func:`typing.get_type_hints` to access the metadata at runtime. (Contributed
|
||||||
|
by Till Varoquaux and Konstantin Kashin.)
|
||||||
|
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -22,6 +22,7 @@ from typing import NewType
|
||||||
from typing import NamedTuple, TypedDict
|
from typing import NamedTuple, TypedDict
|
||||||
from typing import IO, TextIO, BinaryIO
|
from typing import IO, TextIO, BinaryIO
|
||||||
from typing import Pattern, Match
|
from typing import Pattern, Match
|
||||||
|
from typing import Annotated
|
||||||
import abc
|
import abc
|
||||||
import typing
|
import typing
|
||||||
import weakref
|
import weakref
|
||||||
|
@ -2891,6 +2892,64 @@ class GetTypeHintTests(BaseTestCase):
|
||||||
self.assertEqual(gth(ForRefExample.func), expects)
|
self.assertEqual(gth(ForRefExample.func), expects)
|
||||||
self.assertEqual(gth(ForRefExample.nested), expects)
|
self.assertEqual(gth(ForRefExample.nested), expects)
|
||||||
|
|
||||||
|
def test_get_type_hints_annotated(self):
|
||||||
|
def foobar(x: List['X']): ...
|
||||||
|
X = Annotated[int, (1, 10)]
|
||||||
|
self.assertEqual(
|
||||||
|
get_type_hints(foobar, globals(), locals()),
|
||||||
|
{'x': List[int]}
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
get_type_hints(foobar, globals(), locals(), include_extras=True),
|
||||||
|
{'x': List[Annotated[int, (1, 10)]]}
|
||||||
|
)
|
||||||
|
BA = Tuple[Annotated[T, (1, 0)], ...]
|
||||||
|
def barfoo(x: BA): ...
|
||||||
|
self.assertEqual(get_type_hints(barfoo, globals(), locals())['x'], Tuple[T, ...])
|
||||||
|
self.assertIs(
|
||||||
|
get_type_hints(barfoo, globals(), locals(), include_extras=True)['x'],
|
||||||
|
BA
|
||||||
|
)
|
||||||
|
def barfoo2(x: typing.Callable[..., Annotated[List[T], "const"]],
|
||||||
|
y: typing.Union[int, Annotated[T, "mutable"]]): ...
|
||||||
|
self.assertEqual(
|
||||||
|
get_type_hints(barfoo2, globals(), locals()),
|
||||||
|
{'x': typing.Callable[..., List[T]], 'y': typing.Union[int, T]}
|
||||||
|
)
|
||||||
|
BA2 = typing.Callable[..., List[T]]
|
||||||
|
def barfoo3(x: BA2): ...
|
||||||
|
self.assertIs(
|
||||||
|
get_type_hints(barfoo3, globals(), locals(), include_extras=True)["x"],
|
||||||
|
BA2
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_type_hints_annotated_refs(self):
|
||||||
|
|
||||||
|
Const = Annotated[T, "Const"]
|
||||||
|
|
||||||
|
class MySet(Generic[T]):
|
||||||
|
|
||||||
|
def __ior__(self, other: "Const[MySet[T]]") -> "MySet[T]":
|
||||||
|
...
|
||||||
|
|
||||||
|
def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]":
|
||||||
|
...
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
get_type_hints(MySet.__iand__, globals(), locals()),
|
||||||
|
{'other': MySet[T], 'return': MySet[T]}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
get_type_hints(MySet.__iand__, globals(), locals(), include_extras=True),
|
||||||
|
{'other': Const[MySet[T]], 'return': MySet[T]}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
get_type_hints(MySet.__ior__, globals(), locals()),
|
||||||
|
{'other': MySet[T], 'return': MySet[T]}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GetUtilitiesTestCase(TestCase):
|
class GetUtilitiesTestCase(TestCase):
|
||||||
def test_get_origin(self):
|
def test_get_origin(self):
|
||||||
|
@ -2906,6 +2965,7 @@ class GetUtilitiesTestCase(TestCase):
|
||||||
self.assertIs(get_origin(Generic), Generic)
|
self.assertIs(get_origin(Generic), Generic)
|
||||||
self.assertIs(get_origin(Generic[T]), Generic)
|
self.assertIs(get_origin(Generic[T]), Generic)
|
||||||
self.assertIs(get_origin(List[Tuple[T, T]][int]), list)
|
self.assertIs(get_origin(List[Tuple[T, T]][int]), list)
|
||||||
|
self.assertIs(get_origin(Annotated[T, 'thing']), Annotated)
|
||||||
|
|
||||||
def test_get_args(self):
|
def test_get_args(self):
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
@ -2926,6 +2986,7 @@ class GetUtilitiesTestCase(TestCase):
|
||||||
(int, Callable[[Tuple[T, ...]], str]))
|
(int, Callable[[Tuple[T, ...]], str]))
|
||||||
self.assertEqual(get_args(Tuple[int, ...]), (int, ...))
|
self.assertEqual(get_args(Tuple[int, ...]), (int, ...))
|
||||||
self.assertEqual(get_args(Tuple[()]), ((),))
|
self.assertEqual(get_args(Tuple[()]), ((),))
|
||||||
|
self.assertEqual(get_args(Annotated[T, 'one', 2, ['three']]), (T, 'one', 2, ['three']))
|
||||||
|
|
||||||
|
|
||||||
class CollectionsAbcTests(BaseTestCase):
|
class CollectionsAbcTests(BaseTestCase):
|
||||||
|
@ -3844,6 +3905,179 @@ class RETests(BaseTestCase):
|
||||||
"type 're.Match' is not an acceptable base type")
|
"type 're.Match' is not an acceptable base type")
|
||||||
|
|
||||||
|
|
||||||
|
class AnnotatedTests(BaseTestCase):
|
||||||
|
|
||||||
|
def test_repr(self):
|
||||||
|
self.assertEqual(
|
||||||
|
repr(Annotated[int, 4, 5]),
|
||||||
|
"typing.Annotated[int, 4, 5]"
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
repr(Annotated[List[int], 4, 5]),
|
||||||
|
"typing.Annotated[typing.List[int], 4, 5]"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_flatten(self):
|
||||||
|
A = Annotated[Annotated[int, 4], 5]
|
||||||
|
self.assertEqual(A, Annotated[int, 4, 5])
|
||||||
|
self.assertEqual(A.__metadata__, (4, 5))
|
||||||
|
self.assertEqual(A.__origin__, int)
|
||||||
|
|
||||||
|
def test_specialize(self):
|
||||||
|
L = Annotated[List[T], "my decoration"]
|
||||||
|
LI = Annotated[List[int], "my decoration"]
|
||||||
|
self.assertEqual(L[int], Annotated[List[int], "my decoration"])
|
||||||
|
self.assertEqual(L[int].__metadata__, ("my decoration",))
|
||||||
|
self.assertEqual(L[int].__origin__, List[int])
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
LI[int]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
L[int, float]
|
||||||
|
|
||||||
|
def test_hash_eq(self):
|
||||||
|
self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1)
|
||||||
|
self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4])
|
||||||
|
self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5])
|
||||||
|
self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4])
|
||||||
|
self.assertEqual(
|
||||||
|
{Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]},
|
||||||
|
{Annotated[int, 4, 5], Annotated[T, 4, 5]}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_instantiate(self):
|
||||||
|
class C:
|
||||||
|
classvar = 4
|
||||||
|
|
||||||
|
def __init__(self, x):
|
||||||
|
self.x = x
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, C):
|
||||||
|
return NotImplemented
|
||||||
|
return other.x == self.x
|
||||||
|
|
||||||
|
A = Annotated[C, "a decoration"]
|
||||||
|
a = A(5)
|
||||||
|
c = C(5)
|
||||||
|
self.assertEqual(a, c)
|
||||||
|
self.assertEqual(a.x, c.x)
|
||||||
|
self.assertEqual(a.classvar, c.classvar)
|
||||||
|
|
||||||
|
def test_instantiate_generic(self):
|
||||||
|
MyCount = Annotated[typing.Counter[T], "my decoration"]
|
||||||
|
self.assertEqual(MyCount([4, 4, 5]), {4: 2, 5: 1})
|
||||||
|
self.assertEqual(MyCount[int]([4, 4, 5]), {4: 2, 5: 1})
|
||||||
|
|
||||||
|
def test_cannot_instantiate_forward(self):
|
||||||
|
A = Annotated["int", (5, 6)]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
A(5)
|
||||||
|
|
||||||
|
def test_cannot_instantiate_type_var(self):
|
||||||
|
A = Annotated[T, (5, 6)]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
A(5)
|
||||||
|
|
||||||
|
def test_cannot_getattr_typevar(self):
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
Annotated[T, (5, 7)].x
|
||||||
|
|
||||||
|
def test_attr_passthrough(self):
|
||||||
|
class C:
|
||||||
|
classvar = 4
|
||||||
|
|
||||||
|
A = Annotated[C, "a decoration"]
|
||||||
|
self.assertEqual(A.classvar, 4)
|
||||||
|
A.x = 5
|
||||||
|
self.assertEqual(C.x, 5)
|
||||||
|
|
||||||
|
def test_hash_eq(self):
|
||||||
|
self.assertEqual(len({Annotated[int, 4, 5], Annotated[int, 4, 5]}), 1)
|
||||||
|
self.assertNotEqual(Annotated[int, 4, 5], Annotated[int, 5, 4])
|
||||||
|
self.assertNotEqual(Annotated[int, 4, 5], Annotated[str, 4, 5])
|
||||||
|
self.assertNotEqual(Annotated[int, 4], Annotated[int, 4, 4])
|
||||||
|
self.assertEqual(
|
||||||
|
{Annotated[int, 4, 5], Annotated[int, 4, 5], Annotated[T, 4, 5]},
|
||||||
|
{Annotated[int, 4, 5], Annotated[T, 4, 5]}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cannot_subclass(self):
|
||||||
|
with self.assertRaisesRegex(TypeError, "Cannot subclass .*Annotated"):
|
||||||
|
class C(Annotated):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_cannot_check_instance(self):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
isinstance(5, Annotated[int, "positive"])
|
||||||
|
|
||||||
|
def test_cannot_check_subclass(self):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
issubclass(int, Annotated[int, "positive"])
|
||||||
|
|
||||||
|
def test_pickle(self):
|
||||||
|
samples = [typing.Any, typing.Union[int, str],
|
||||||
|
typing.Optional[str], Tuple[int, ...],
|
||||||
|
typing.Callable[[str], bytes]]
|
||||||
|
|
||||||
|
for t in samples:
|
||||||
|
x = Annotated[t, "a"]
|
||||||
|
|
||||||
|
for prot in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||||
|
with self.subTest(protocol=prot, type=t):
|
||||||
|
pickled = pickle.dumps(x, prot)
|
||||||
|
restored = pickle.loads(pickled)
|
||||||
|
self.assertEqual(x, restored)
|
||||||
|
|
||||||
|
global _Annotated_test_G
|
||||||
|
|
||||||
|
class _Annotated_test_G(Generic[T]):
|
||||||
|
x = 1
|
||||||
|
|
||||||
|
G = Annotated[_Annotated_test_G[int], "A decoration"]
|
||||||
|
G.foo = 42
|
||||||
|
G.bar = 'abc'
|
||||||
|
|
||||||
|
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||||
|
z = pickle.dumps(G, proto)
|
||||||
|
x = pickle.loads(z)
|
||||||
|
self.assertEqual(x.foo, 42)
|
||||||
|
self.assertEqual(x.bar, 'abc')
|
||||||
|
self.assertEqual(x.x, 1)
|
||||||
|
|
||||||
|
def test_subst(self):
|
||||||
|
dec = "a decoration"
|
||||||
|
dec2 = "another decoration"
|
||||||
|
|
||||||
|
S = Annotated[T, dec2]
|
||||||
|
self.assertEqual(S[int], Annotated[int, dec2])
|
||||||
|
|
||||||
|
self.assertEqual(S[Annotated[int, dec]], Annotated[int, dec, dec2])
|
||||||
|
L = Annotated[List[T], dec]
|
||||||
|
|
||||||
|
self.assertEqual(L[int], Annotated[List[int], dec])
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
L[int, int]
|
||||||
|
|
||||||
|
self.assertEqual(S[L[int]], Annotated[List[int], dec, dec2])
|
||||||
|
|
||||||
|
D = Annotated[typing.Dict[KT, VT], dec]
|
||||||
|
self.assertEqual(D[str, int], Annotated[typing.Dict[str, int], dec])
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
D[int]
|
||||||
|
|
||||||
|
It = Annotated[int, dec]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
It[None]
|
||||||
|
|
||||||
|
LI = L[int]
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
LI[None]
|
||||||
|
|
||||||
|
def test_annotated_in_other_types(self):
|
||||||
|
X = List[Annotated[T, 5]]
|
||||||
|
self.assertEqual(X[int], List[Annotated[int, 5]])
|
||||||
|
|
||||||
|
|
||||||
class AllTests(BaseTestCase):
|
class AllTests(BaseTestCase):
|
||||||
"""Tests for __all__."""
|
"""Tests for __all__."""
|
||||||
|
|
||||||
|
|
126
Lib/typing.py
126
Lib/typing.py
|
@ -31,6 +31,7 @@ from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType
|
||||||
# Please keep __all__ alphabetized within each category.
|
# Please keep __all__ alphabetized within each category.
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Super-special typing primitives.
|
# Super-special typing primitives.
|
||||||
|
'Annotated',
|
||||||
'Any',
|
'Any',
|
||||||
'Callable',
|
'Callable',
|
||||||
'ClassVar',
|
'ClassVar',
|
||||||
|
@ -1118,6 +1119,101 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
|
||||||
cls.__init__ = _no_init
|
cls.__init__ = _no_init
|
||||||
|
|
||||||
|
|
||||||
|
class _AnnotatedAlias(_GenericAlias, _root=True):
|
||||||
|
"""Runtime representation of an annotated type.
|
||||||
|
|
||||||
|
At its core 'Annotated[t, dec1, dec2, ...]' is an alias for the type 't'
|
||||||
|
with extra annotations. The alias behaves like a normal typing alias,
|
||||||
|
instantiating is the same as instantiating the underlying type, binding
|
||||||
|
it to types is also the same.
|
||||||
|
"""
|
||||||
|
def __init__(self, origin, metadata):
|
||||||
|
if isinstance(origin, _AnnotatedAlias):
|
||||||
|
metadata = origin.__metadata__ + metadata
|
||||||
|
origin = origin.__origin__
|
||||||
|
super().__init__(origin, origin)
|
||||||
|
self.__metadata__ = metadata
|
||||||
|
|
||||||
|
def copy_with(self, params):
|
||||||
|
assert len(params) == 1
|
||||||
|
new_type = params[0]
|
||||||
|
return _AnnotatedAlias(new_type, self.__metadata__)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "typing.Annotated[{}, {}]".format(
|
||||||
|
_type_repr(self.__origin__),
|
||||||
|
", ".join(repr(a) for a in self.__metadata__)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
return operator.getitem, (
|
||||||
|
Annotated, (self.__origin__,) + self.__metadata__
|
||||||
|
)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, _AnnotatedAlias):
|
||||||
|
return NotImplemented
|
||||||
|
if self.__origin__ != other.__origin__:
|
||||||
|
return False
|
||||||
|
return self.__metadata__ == other.__metadata__
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.__origin__, self.__metadata__))
|
||||||
|
|
||||||
|
|
||||||
|
class Annotated:
|
||||||
|
"""Add context specific metadata to a type.
|
||||||
|
|
||||||
|
Example: Annotated[int, runtime_check.Unsigned] indicates to the
|
||||||
|
hypothetical runtime_check module that this type is an unsigned int.
|
||||||
|
Every other consumer of this type can ignore this metadata and treat
|
||||||
|
this type as int.
|
||||||
|
|
||||||
|
The first argument to Annotated must be a valid type.
|
||||||
|
|
||||||
|
Details:
|
||||||
|
|
||||||
|
- It's an error to call `Annotated` with less than two arguments.
|
||||||
|
- Nested Annotated are flattened::
|
||||||
|
|
||||||
|
Annotated[Annotated[T, Ann1, Ann2], Ann3] == Annotated[T, Ann1, Ann2, Ann3]
|
||||||
|
|
||||||
|
- Instantiating an annotated type is equivalent to instantiating the
|
||||||
|
underlying type::
|
||||||
|
|
||||||
|
Annotated[C, Ann1](5) == C(5)
|
||||||
|
|
||||||
|
- Annotated can be used as a generic type alias::
|
||||||
|
|
||||||
|
Optimized = Annotated[T, runtime.Optimize()]
|
||||||
|
Optimized[int] == Annotated[int, runtime.Optimize()]
|
||||||
|
|
||||||
|
OptimizedList = Annotated[List[T], runtime.Optimize()]
|
||||||
|
OptimizedList[int] == Annotated[List[int], runtime.Optimize()]
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
raise TypeError("Type Annotated cannot be instantiated.")
|
||||||
|
|
||||||
|
@_tp_cache
|
||||||
|
def __class_getitem__(cls, params):
|
||||||
|
if not isinstance(params, tuple) or len(params) < 2:
|
||||||
|
raise TypeError("Annotated[...] should be used "
|
||||||
|
"with at least two arguments (a type and an "
|
||||||
|
"annotation).")
|
||||||
|
msg = "Annotated[t, ...]: t must be a type."
|
||||||
|
origin = _type_check(params[0], msg)
|
||||||
|
metadata = tuple(params[1:])
|
||||||
|
return _AnnotatedAlias(origin, metadata)
|
||||||
|
|
||||||
|
def __init_subclass__(cls, *args, **kwargs):
|
||||||
|
raise TypeError(
|
||||||
|
"Cannot subclass {}.Annotated".format(cls.__module__)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def runtime_checkable(cls):
|
def runtime_checkable(cls):
|
||||||
"""Mark a protocol class as a runtime protocol.
|
"""Mark a protocol class as a runtime protocol.
|
||||||
|
|
||||||
|
@ -1179,12 +1275,13 @@ _allowed_types = (types.FunctionType, types.BuiltinFunctionType,
|
||||||
WrapperDescriptorType, MethodWrapperType, MethodDescriptorType)
|
WrapperDescriptorType, MethodWrapperType, MethodDescriptorType)
|
||||||
|
|
||||||
|
|
||||||
def get_type_hints(obj, globalns=None, localns=None):
|
def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
|
||||||
"""Return type hints for an object.
|
"""Return type hints for an object.
|
||||||
|
|
||||||
This is often the same as obj.__annotations__, but it handles
|
This is often the same as obj.__annotations__, but it handles
|
||||||
forward references encoded as string literals, and if necessary
|
forward references encoded as string literals, adds Optional[t] if a
|
||||||
adds Optional[t] if a default value equal to None is set.
|
default value equal to None is set and recursively replaces all
|
||||||
|
'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
|
||||||
|
|
||||||
The argument may be a module, class, method, or function. The annotations
|
The argument may be a module, class, method, or function. The annotations
|
||||||
are returned as a dictionary. For classes, annotations include also
|
are returned as a dictionary. For classes, annotations include also
|
||||||
|
@ -1228,7 +1325,7 @@ def get_type_hints(obj, globalns=None, localns=None):
|
||||||
value = ForwardRef(value, is_argument=False)
|
value = ForwardRef(value, is_argument=False)
|
||||||
value = _eval_type(value, base_globals, localns)
|
value = _eval_type(value, base_globals, localns)
|
||||||
hints[name] = value
|
hints[name] = value
|
||||||
return hints
|
return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
|
||||||
|
|
||||||
if globalns is None:
|
if globalns is None:
|
||||||
if isinstance(obj, types.ModuleType):
|
if isinstance(obj, types.ModuleType):
|
||||||
|
@ -1262,7 +1359,22 @@ def get_type_hints(obj, globalns=None, localns=None):
|
||||||
if name in defaults and defaults[name] is None:
|
if name in defaults and defaults[name] is None:
|
||||||
value = Optional[value]
|
value = Optional[value]
|
||||||
hints[name] = value
|
hints[name] = value
|
||||||
return hints
|
return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def _strip_annotations(t):
|
||||||
|
"""Strips the annotations from a given type.
|
||||||
|
"""
|
||||||
|
if isinstance(t, _AnnotatedAlias):
|
||||||
|
return _strip_annotations(t.__origin__)
|
||||||
|
if isinstance(t, _GenericAlias):
|
||||||
|
stripped_args = tuple(_strip_annotations(a) for a in t.__args__)
|
||||||
|
if stripped_args == t.__args__:
|
||||||
|
return t
|
||||||
|
res = t.copy_with(stripped_args)
|
||||||
|
res._special = t._special
|
||||||
|
return res
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
def get_origin(tp):
|
def get_origin(tp):
|
||||||
|
@ -1279,6 +1391,8 @@ def get_origin(tp):
|
||||||
get_origin(Union[T, int]) is Union
|
get_origin(Union[T, int]) is Union
|
||||||
get_origin(List[Tuple[T, T]][int]) == list
|
get_origin(List[Tuple[T, T]][int]) == list
|
||||||
"""
|
"""
|
||||||
|
if isinstance(tp, _AnnotatedAlias):
|
||||||
|
return Annotated
|
||||||
if isinstance(tp, _GenericAlias):
|
if isinstance(tp, _GenericAlias):
|
||||||
return tp.__origin__
|
return tp.__origin__
|
||||||
if tp is Generic:
|
if tp is Generic:
|
||||||
|
@ -1297,6 +1411,8 @@ def get_args(tp):
|
||||||
get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
|
get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
|
||||||
get_args(Callable[[], T][int]) == ([], int)
|
get_args(Callable[[], T][int]) == ([], int)
|
||||||
"""
|
"""
|
||||||
|
if isinstance(tp, _AnnotatedAlias):
|
||||||
|
return (tp.__origin__,) + tp.__metadata__
|
||||||
if isinstance(tp, _GenericAlias):
|
if isinstance(tp, _GenericAlias):
|
||||||
res = tp.__args__
|
res = tp.__args__
|
||||||
if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
|
if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Add :data:`typing.Annotated` and ``include_extras`` parameter to
|
||||||
|
:func:`typing.get_type_hints` as part of :pep:`593`. Patch by Till
|
||||||
|
Varoquaux, documentation by Till Varoquaux and Konstantin Kashin.
|
Loading…
Reference in New Issue