Skip to content

Commit 4c23aff

Browse files
authored
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.
1 parent 2a58b06 commit 4c23aff

File tree

4 files changed

+99
-0
lines changed

4 files changed

+99
-0
lines changed

Doc/library/typing.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,25 @@ The module defines the following classes, functions and decorators:
10211021
a dictionary constructed by merging all the ``__annotations__`` along
10221022
``C.__mro__`` in reverse order.
10231023

1024+
.. function:: get_origin(typ)
1025+
.. function:: get_args(typ)
1026+
1027+
Provide basic introspection for generic types and special typing forms.
1028+
1029+
For a typing object of the form ``X[Y, Z, ...]`` these functions return
1030+
``X`` and ``(Y, Z, ...)``. If ``X`` is a generic alias for a builtin or
1031+
:mod:`collections` class, it gets normalized to the original class.
1032+
For unsupported objects return ``None`` and ``()`` correspondingly.
1033+
Examples::
1034+
1035+
assert get_origin(Dict[str, int]) is dict
1036+
assert get_args(Dict[int, str]) == (int, str)
1037+
1038+
assert get_origin(Union[int, str]) is Union
1039+
assert get_args(Union[int, str]) == (int, str)
1040+
1041+
.. versionadded:: 3.8
1042+
10241043
.. decorator:: overload
10251044

10261045
The ``@overload`` decorator allows describing functions and methods

Lib/test/test_typing.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typing import Generic, ClassVar, Final, final, Protocol
1616
from typing import cast, runtime_checkable
1717
from typing import get_type_hints
18+
from typing import get_origin, get_args
1819
from typing import no_type_check, no_type_check_decorator
1920
from typing import Type
2021
from typing import NewType
@@ -2735,6 +2736,42 @@ def test_get_type_hints_ClassVar(self):
27352736
self.assertEqual(gth(G), {'lst': ClassVar[List[T]]})
27362737

27372738

2739+
class GetUtilitiesTestCase(TestCase):
2740+
def test_get_origin(self):
2741+
T = TypeVar('T')
2742+
class C(Generic[T]): pass
2743+
self.assertIs(get_origin(C[int]), C)
2744+
self.assertIs(get_origin(C[T]), C)
2745+
self.assertIs(get_origin(int), None)
2746+
self.assertIs(get_origin(ClassVar[int]), ClassVar)
2747+
self.assertIs(get_origin(Union[int, str]), Union)
2748+
self.assertIs(get_origin(Literal[42, 43]), Literal)
2749+
self.assertIs(get_origin(Final[List[int]]), Final)
2750+
self.assertIs(get_origin(Generic), Generic)
2751+
self.assertIs(get_origin(Generic[T]), Generic)
2752+
self.assertIs(get_origin(List[Tuple[T, T]][int]), list)
2753+
2754+
def test_get_args(self):
2755+
T = TypeVar('T')
2756+
class C(Generic[T]): pass
2757+
self.assertEqual(get_args(C[int]), (int,))
2758+
self.assertEqual(get_args(C[T]), (T,))
2759+
self.assertEqual(get_args(int), ())
2760+
self.assertEqual(get_args(ClassVar[int]), (int,))
2761+
self.assertEqual(get_args(Union[int, str]), (int, str))
2762+
self.assertEqual(get_args(Literal[42, 43]), (42, 43))
2763+
self.assertEqual(get_args(Final[List[int]]), (List[int],))
2764+
self.assertEqual(get_args(Union[int, Tuple[T, int]][str]),
2765+
(int, Tuple[str, int]))
2766+
self.assertEqual(get_args(typing.Dict[int, Tuple[T, T]][Optional[int]]),
2767+
(int, Tuple[Optional[int], Optional[int]]))
2768+
self.assertEqual(get_args(Callable[[], T][int]), ([], int,))
2769+
self.assertEqual(get_args(Union[int, Callable[[Tuple[T, ...]], str]]),
2770+
(int, Callable[[Tuple[T, ...]], str]))
2771+
self.assertEqual(get_args(Tuple[int, ...]), (int, ...))
2772+
self.assertEqual(get_args(Tuple[()]), ((),))
2773+
2774+
27382775
class CollectionsAbcTests(BaseTestCase):
27392776

27402777
def test_hashable(self):

Lib/typing.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@
9999
'AnyStr',
100100
'cast',
101101
'final',
102+
'get_args',
103+
'get_origin',
102104
'get_type_hints',
103105
'NewType',
104106
'no_type_check',
@@ -1253,6 +1255,46 @@ def get_type_hints(obj, globalns=None, localns=None):
12531255
return hints
12541256

12551257

1258+
def get_origin(tp):
1259+
"""Get the unsubscripted version of a type.
1260+
1261+
This supports generic types, Callable, Tuple, Union, Literal, Final and ClassVar.
1262+
Return None for unsupported types. Examples::
1263+
1264+
get_origin(Literal[42]) is Literal
1265+
get_origin(int) is None
1266+
get_origin(ClassVar[int]) is ClassVar
1267+
get_origin(Generic) is Generic
1268+
get_origin(Generic[T]) is Generic
1269+
get_origin(Union[T, int]) is Union
1270+
get_origin(List[Tuple[T, T]][int]) == list
1271+
"""
1272+
if isinstance(tp, _GenericAlias):
1273+
return tp.__origin__
1274+
if tp is Generic:
1275+
return Generic
1276+
return None
1277+
1278+
1279+
def get_args(tp):
1280+
"""Get type arguments with all substitutions performed.
1281+
1282+
For unions, basic simplifications used by Union constructor are performed.
1283+
Examples::
1284+
get_args(Dict[str, int]) == (str, int)
1285+
get_args(int) == ()
1286+
get_args(Union[int, Union[T, int], str][int]) == (int, str)
1287+
get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
1288+
get_args(Callable[[], T][int]) == ([], int)
1289+
"""
1290+
if isinstance(tp, _GenericAlias):
1291+
res = tp.__args__
1292+
if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
1293+
res = (list(res[:-1]), res[-1])
1294+
return res
1295+
return ()
1296+
1297+
12561298
def no_type_check(arg):
12571299
"""Decorator to indicate that annotations are not type hints.
12581300
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add ``get_origin()`` and ``get_args()`` introspection helpers to ``typing`` module.

0 commit comments

Comments
 (0)