[Python-checkins] gh-91621: Fix typing.get_type_hints for collections.abc.Callable (#91656)

JelleZijlstra webhook-mailer at python.org
Mon May 2 19:08:33 EDT 2022


https://github.com/python/cpython/commit/ebb8b512e959ca1a0a506ac0f0faf461b1b2ff85
commit: ebb8b512e959ca1a0a506ac0f0faf461b1b2ff85
branch: main
author: Shantanu <12621235+hauntsaninja at users.noreply.github.com>
committer: JelleZijlstra <jelle.zijlstra at gmail.com>
date: 2022-05-02T17:08:28-06:00
summary:

gh-91621: Fix typing.get_type_hints for collections.abc.Callable (#91656)

This mirrors logic in typing.get_args. The trickiness comes from how we
flatten args in collections.abc.Callable, see
https://bugs.python.org/issue42195

files:
A Misc/NEWS.d/next/Library/2022-04-18-18-55-21.gh-issue-91621.ACNlda.rst
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index c074e7a780020..08f7d0211eafb 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -4876,6 +4876,17 @@ def test_get_type_hints_typeddict(self):
             'a': Annotated[Required[int], "a", "b", "c"]
         })
 
+    def test_get_type_hints_collections_abc_callable(self):
+        # https://github.com/python/cpython/issues/91621
+        P = ParamSpec('P')
+        def f(x: collections.abc.Callable[[int], int]): ...
+        def g(x: collections.abc.Callable[..., int]): ...
+        def h(x: collections.abc.Callable[P, int]): ...
+
+        self.assertEqual(get_type_hints(f), {'x': collections.abc.Callable[[int], int]})
+        self.assertEqual(get_type_hints(g), {'x': collections.abc.Callable[..., int]})
+        self.assertEqual(get_type_hints(h), {'x': collections.abc.Callable[P, int]})
+
 
 class GetUtilitiesTestCase(TestCase):
     def test_get_origin(self):
diff --git a/Lib/typing.py b/Lib/typing.py
index 29a3f43881b16..84f0fd1a8a946 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -203,6 +203,24 @@ def _is_param_expr(arg):
             (tuple, list, ParamSpec, _ConcatenateGenericAlias))
 
 
+def _should_unflatten_callable_args(typ, args):
+    """Internal helper for munging collections.abc.Callable's __args__.
+
+    The canonical representation for a Callable's __args__ flattens the
+    argument types, see https://bugs.python.org/issue42195. For example:
+
+        collections.abc.Callable[[int, int], str].__args__ == (int, int, str)
+        collections.abc.Callable[ParamSpec, str].__args__ == (ParamSpec, str)
+
+    As a result, if we need to reconstruct the Callable from its __args__,
+    we need to unflatten it.
+    """
+    return (
+        typ.__origin__ is collections.abc.Callable
+        and not (len(args) == 2 and _is_param_expr(args[0]))
+    )
+
+
 def _type_repr(obj):
     """Return the repr() of an object, special-casing types (internal helper).
 
@@ -351,7 +369,10 @@ def _eval_type(t, globalns, localns, recursive_guard=frozenset()):
                 ForwardRef(arg) if isinstance(arg, str) else arg
                 for arg in t.__args__
             )
-            t = t.__origin__[args]
+            if _should_unflatten_callable_args(t, args):
+                t = t.__origin__[(args[:-1], args[-1])]
+            else:
+                t = t.__origin__[args]
         ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)
         if ev_args == t.__args__:
             return t
@@ -2361,8 +2382,7 @@ def get_args(tp):
         return (tp.__origin__,) + tp.__metadata__
     if isinstance(tp, (_GenericAlias, GenericAlias)):
         res = tp.__args__
-        if (tp.__origin__ is collections.abc.Callable
-                and not (len(res) == 2 and _is_param_expr(res[0]))):
+        if _should_unflatten_callable_args(tp, res):
             res = (list(res[:-1]), res[-1])
         return res
     if isinstance(tp, types.UnionType):
diff --git a/Misc/NEWS.d/next/Library/2022-04-18-18-55-21.gh-issue-91621.ACNlda.rst b/Misc/NEWS.d/next/Library/2022-04-18-18-55-21.gh-issue-91621.ACNlda.rst
new file mode 100644
index 0000000000000..b9e68d225a25b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-04-18-18-55-21.gh-issue-91621.ACNlda.rst
@@ -0,0 +1 @@
+Fix :func:`typing.get_type_hints` for :class:`collections.abc.Callable`. Patch by Shantanu Jain.



More information about the Python-checkins mailing list