Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-29262: Add get_origin() and get_args() introspection helpers to typing #13685

Merged
merged 4 commits into from
May 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,25 @@ The module defines the following classes, functions and decorators:
a dictionary constructed by merging all the ``__annotations__`` along
``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

The ``@overload`` decorator allows describing functions and methods
Expand Down
37 changes: 37 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing import Generic, ClassVar, Final, final, Protocol
from typing import cast, runtime_checkable
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 Type
from typing import NewType
Expand Down Expand Up @@ -2735,6 +2736,42 @@ def test_get_type_hints_ClassVar(self):
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):

def test_hashable(self):
Expand Down
42 changes: 42 additions & 0 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@
'AnyStr',
'cast',
'final',
'get_args',
'get_origin',
'get_type_hints',
'NewType',
'no_type_check',
Expand Down Expand Up @@ -1253,6 +1255,46 @@ def get_type_hints(obj, globalns=None, localns=None):
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):
"""Decorator to indicate that annotations are not type hints.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``get_origin()`` and ``get_args()`` introspection helpers to ``typing`` module.