bpo-29262: Add get_origin() and get_args() introspection helpers to typing (GH-13685)
This is an old feature request that appears from time to time. After a year of experimenting with various introspection capabilities in `typing_inspect` on PyPI, I propose to add these two most commonly used functions: `get_origin()` and `get_args()`. These are essentially thin public wrappers around private APIs: `__origin__` and `__args__`. As discussed in the issue and on the typing tracker, exposing some public helpers instead of `__origin__` and `__args__` directly will give us more flexibility if we will decide to update the internal representation, while still maintaining backwards compatibility. The implementation is very simple an is essentially a copy from `typing_inspect` with one exception: `ClassVar` was special-cased in `typing_inspect`, but I think this special-casing doesn't really help and only makes things more complicated.
This commit is contained in:
parent
2a58b0636d
commit
4c23aff065
|
@ -1021,6 +1021,25 @@ 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.
|
||||||
|
|
||||||
|
.. function:: get_origin(typ)
|
||||||
|
.. function:: get_args(typ)
|
||||||
|
|
||||||
|
Provide basic introspection for generic types and special typing forms.
|
||||||
|
|
||||||
|
For a typing object of the form ``X[Y, Z, ...]`` these functions return
|
||||||
|
``X`` and ``(Y, Z, ...)``. If ``X`` is a generic alias for a builtin or
|
||||||
|
:mod:`collections` class, it gets normalized to the original class.
|
||||||
|
For unsupported objects return ``None`` and ``()`` correspondingly.
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
assert get_origin(Dict[str, int]) is dict
|
||||||
|
assert get_args(Dict[int, str]) == (int, str)
|
||||||
|
|
||||||
|
assert get_origin(Union[int, str]) is Union
|
||||||
|
assert get_args(Union[int, str]) == (int, str)
|
||||||
|
|
||||||
|
.. versionadded:: 3.8
|
||||||
|
|
||||||
.. decorator:: overload
|
.. decorator:: overload
|
||||||
|
|
||||||
The ``@overload`` decorator allows describing functions and methods
|
The ``@overload`` decorator allows describing functions and methods
|
||||||
|
|
|
@ -15,6 +15,7 @@ from typing import Callable
|
||||||
from typing import Generic, ClassVar, Final, final, Protocol
|
from typing import Generic, ClassVar, Final, final, Protocol
|
||||||
from typing import cast, runtime_checkable
|
from typing import cast, runtime_checkable
|
||||||
from typing import get_type_hints
|
from typing import get_type_hints
|
||||||
|
from typing import get_origin, get_args
|
||||||
from typing import no_type_check, no_type_check_decorator
|
from typing import no_type_check, no_type_check_decorator
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from typing import NewType
|
from typing import NewType
|
||||||
|
@ -2735,6 +2736,42 @@ class GetTypeHintTests(BaseTestCase):
|
||||||
self.assertEqual(gth(G), {'lst': ClassVar[List[T]]})
|
self.assertEqual(gth(G), {'lst': ClassVar[List[T]]})
|
||||||
|
|
||||||
|
|
||||||
|
class GetUtilitiesTestCase(TestCase):
|
||||||
|
def test_get_origin(self):
|
||||||
|
T = TypeVar('T')
|
||||||
|
class C(Generic[T]): pass
|
||||||
|
self.assertIs(get_origin(C[int]), C)
|
||||||
|
self.assertIs(get_origin(C[T]), C)
|
||||||
|
self.assertIs(get_origin(int), None)
|
||||||
|
self.assertIs(get_origin(ClassVar[int]), ClassVar)
|
||||||
|
self.assertIs(get_origin(Union[int, str]), Union)
|
||||||
|
self.assertIs(get_origin(Literal[42, 43]), Literal)
|
||||||
|
self.assertIs(get_origin(Final[List[int]]), Final)
|
||||||
|
self.assertIs(get_origin(Generic), Generic)
|
||||||
|
self.assertIs(get_origin(Generic[T]), Generic)
|
||||||
|
self.assertIs(get_origin(List[Tuple[T, T]][int]), list)
|
||||||
|
|
||||||
|
def test_get_args(self):
|
||||||
|
T = TypeVar('T')
|
||||||
|
class C(Generic[T]): pass
|
||||||
|
self.assertEqual(get_args(C[int]), (int,))
|
||||||
|
self.assertEqual(get_args(C[T]), (T,))
|
||||||
|
self.assertEqual(get_args(int), ())
|
||||||
|
self.assertEqual(get_args(ClassVar[int]), (int,))
|
||||||
|
self.assertEqual(get_args(Union[int, str]), (int, str))
|
||||||
|
self.assertEqual(get_args(Literal[42, 43]), (42, 43))
|
||||||
|
self.assertEqual(get_args(Final[List[int]]), (List[int],))
|
||||||
|
self.assertEqual(get_args(Union[int, Tuple[T, int]][str]),
|
||||||
|
(int, Tuple[str, int]))
|
||||||
|
self.assertEqual(get_args(typing.Dict[int, Tuple[T, T]][Optional[int]]),
|
||||||
|
(int, Tuple[Optional[int], Optional[int]]))
|
||||||
|
self.assertEqual(get_args(Callable[[], T][int]), ([], int,))
|
||||||
|
self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]),
|
||||||
|
(int, Callable[[Tuple[T, ...]], str]))
|
||||||
|
self.assertEqual(get_args(Tuple[int, ...]), (int, ...))
|
||||||
|
self.assertEqual(get_args(Tuple[()]), ((),))
|
||||||
|
|
||||||
|
|
||||||
class CollectionsAbcTests(BaseTestCase):
|
class CollectionsAbcTests(BaseTestCase):
|
||||||
|
|
||||||
def test_hashable(self):
|
def test_hashable(self):
|
||||||
|
|
|
@ -99,6 +99,8 @@ __all__ = [
|
||||||
'AnyStr',
|
'AnyStr',
|
||||||
'cast',
|
'cast',
|
||||||
'final',
|
'final',
|
||||||
|
'get_args',
|
||||||
|
'get_origin',
|
||||||
'get_type_hints',
|
'get_type_hints',
|
||||||
'NewType',
|
'NewType',
|
||||||
'no_type_check',
|
'no_type_check',
|
||||||
|
@ -1253,6 +1255,46 @@ def get_type_hints(obj, globalns=None, localns=None):
|
||||||
return hints
|
return hints
|
||||||
|
|
||||||
|
|
||||||
|
def get_origin(tp):
|
||||||
|
"""Get the unsubscripted version of a type.
|
||||||
|
|
||||||
|
This supports generic types, Callable, Tuple, Union, Literal, Final and ClassVar.
|
||||||
|
Return None for unsupported types. Examples::
|
||||||
|
|
||||||
|
get_origin(Literal[42]) is Literal
|
||||||
|
get_origin(int) is None
|
||||||
|
get_origin(ClassVar[int]) is ClassVar
|
||||||
|
get_origin(Generic) is Generic
|
||||||
|
get_origin(Generic[T]) is Generic
|
||||||
|
get_origin(Union[T, int]) is Union
|
||||||
|
get_origin(List[Tuple[T, T]][int]) == list
|
||||||
|
"""
|
||||||
|
if isinstance(tp, _GenericAlias):
|
||||||
|
return tp.__origin__
|
||||||
|
if tp is Generic:
|
||||||
|
return Generic
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_args(tp):
|
||||||
|
"""Get type arguments with all substitutions performed.
|
||||||
|
|
||||||
|
For unions, basic simplifications used by Union constructor are performed.
|
||||||
|
Examples::
|
||||||
|
get_args(Dict[str, int]) == (str, int)
|
||||||
|
get_args(int) == ()
|
||||||
|
get_args(Union[int, Union[T, int], str][int]) == (int, str)
|
||||||
|
get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
|
||||||
|
get_args(Callable[[], T][int]) == ([], int)
|
||||||
|
"""
|
||||||
|
if isinstance(tp, _GenericAlias):
|
||||||
|
res = tp.__args__
|
||||||
|
if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
|
||||||
|
res = (list(res[:-1]), res[-1])
|
||||||
|
return res
|
||||||
|
return ()
|
||||||
|
|
||||||
|
|
||||||
def no_type_check(arg):
|
def no_type_check(arg):
|
||||||
"""Decorator to indicate that annotations are not type hints.
|
"""Decorator to indicate that annotations are not type hints.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Add ``get_origin()`` and ``get_args()`` introspection helpers to ``typing`` module.
|
Loading…
Reference in New Issue