[Python-checkins] bpo-46195: Do not add `Optional` in `get_type_hints` (GH-30304)

JelleZijlstra webhook-mailer at python.org
Wed Mar 2 00:30:28 EST 2022


https://github.com/python/cpython/commit/20a1c8ee4bcb1c421b7cca1f3f5d6ad7ce30a9c9
commit: 20a1c8ee4bcb1c421b7cca1f3f5d6ad7ce30a9c9
branch: main
author: Nikita Sobolev <mail at sobolevn.me>
committer: JelleZijlstra <jelle.zijlstra at gmail.com>
date: 2022-03-01T21:29:46-08:00
summary:

bpo-46195: Do not add `Optional` in `get_type_hints` (GH-30304)

Co-authored-by: Ken Jin <28750310+Fidget-Spinner at users.noreply.github.com>
Co-authored-by: Jelle Zijlstra <jelle.zijlstra at gmail.com>

files:
A Misc/NEWS.d/next/Library/2021-12-30-21-38-51.bpo-46195.jFKGq_.rst
M Doc/library/typing.rst
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index a02ad244d9fbb..bfcbeb8c7e680 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -2185,9 +2185,7 @@ Introspection helpers
 
    This is often the same as ``obj.__annotations__``. In addition,
    forward references encoded as string literals are handled by evaluating
-   them in ``globals`` and ``locals`` namespaces. If necessary,
-   ``Optional[t]`` is added for function and method annotations if a default
-   value equal to ``None`` is set. For a class ``C``, return
+   them in ``globals`` and ``locals`` namespaces. For a class ``C``, return
    a dictionary constructed by merging all the ``__annotations__`` along
    ``C.__mro__`` in reverse order.
 
@@ -2214,6 +2212,11 @@ Introspection helpers
    .. versionchanged:: 3.9
       Added ``include_extras`` parameter as part of :pep:`593`.
 
+   .. versionchanged:: 3.11
+      Previously, ``Optional[t]`` was added for function and method annotations
+      if a default value equal to ``None`` was set.
+      Now the annotation is returned unchanged.
+
 .. function:: get_args(tp)
 .. function:: get_origin(tp)
 
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index dc1514d63b777..8fcc24c25eb95 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -2828,16 +2828,15 @@ def add_right(self, node: 'Node[T]' = None):
         t = Node[int]
         both_hints = get_type_hints(t.add_both, globals(), locals())
         self.assertEqual(both_hints['left'], Optional[Node[T]])
-        self.assertEqual(both_hints['right'], Optional[Node[T]])
-        self.assertEqual(both_hints['left'], both_hints['right'])
-        self.assertEqual(both_hints['stuff'], Optional[int])
+        self.assertEqual(both_hints['right'], Node[T])
+        self.assertEqual(both_hints['stuff'], int)
         self.assertNotIn('blah', both_hints)
 
         left_hints = get_type_hints(t.add_left, globals(), locals())
         self.assertEqual(left_hints['node'], Optional[Node[T]])
 
         right_hints = get_type_hints(t.add_right, globals(), locals())
-        self.assertEqual(right_hints['node'], Optional[Node[T]])
+        self.assertEqual(right_hints['node'], Node[T])
 
     def test_forwardref_instance_type_error(self):
         fr = typing.ForwardRef('int')
@@ -3630,6 +3629,18 @@ def __iand__(self, other: Const["MySet[T]"]) -> "MySet[T]":
             {'other': MySet[T], 'return': MySet[T]}
         )
 
+    def test_get_type_hints_annotated_with_none_default(self):
+        # See: https://bugs.python.org/issue46195
+        def annotated_with_none_default(x: Annotated[int, 'data'] = None): ...
+        self.assertEqual(
+            get_type_hints(annotated_with_none_default),
+            {'x': int},
+        )
+        self.assertEqual(
+            get_type_hints(annotated_with_none_default, include_extras=True),
+            {'x': Annotated[int, 'data']},
+        )
+
     def test_get_type_hints_classes_str_annotations(self):
         class Foo:
             y = str
diff --git a/Lib/typing.py b/Lib/typing.py
index ad1435ed23d27..9d668b3cf4a2a 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1879,26 +1879,6 @@ def cast(typ, val):
     return val
 
 
-def _get_defaults(func):
-    """Internal helper to extract the default arguments, by name."""
-    try:
-        code = func.__code__
-    except AttributeError:
-        # Some built-in functions don't have __code__, __defaults__, etc.
-        return {}
-    pos_count = code.co_argcount
-    arg_names = code.co_varnames
-    arg_names = arg_names[:pos_count]
-    defaults = func.__defaults__ or ()
-    kwdefaults = func.__kwdefaults__
-    res = dict(kwdefaults) if kwdefaults else {}
-    pos_offset = pos_count - len(defaults)
-    for name, value in zip(arg_names[pos_offset:], defaults):
-        assert name not in res
-        res[name] = value
-    return res
-
-
 _allowed_types = (types.FunctionType, types.BuiltinFunctionType,
                   types.MethodType, types.ModuleType,
                   WrapperDescriptorType, MethodWrapperType, MethodDescriptorType)
@@ -1908,8 +1888,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
     """Return type hints for an object.
 
     This is often the same as obj.__annotations__, but it handles
-    forward references encoded as string literals, adds Optional[t] if a
-    default value equal to None is set and recursively replaces all
+    forward references encoded as string literals and recursively replaces all
     'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
 
     The argument may be a module, class, method, or function. The annotations
@@ -1989,7 +1968,6 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
         else:
             raise TypeError('{!r} is not a module, class, method, '
                             'or function.'.format(obj))
-    defaults = _get_defaults(obj)
     hints = dict(hints)
     for name, value in hints.items():
         if value is None:
@@ -2002,10 +1980,7 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
                 is_argument=not isinstance(obj, types.ModuleType),
                 is_class=False,
             )
-        value = _eval_type(value, globalns, localns)
-        if name in defaults and defaults[name] is None:
-            value = Optional[value]
-        hints[name] = value
+        hints[name] = _eval_type(value, globalns, localns)
     return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
 
 
diff --git a/Misc/NEWS.d/next/Library/2021-12-30-21-38-51.bpo-46195.jFKGq_.rst b/Misc/NEWS.d/next/Library/2021-12-30-21-38-51.bpo-46195.jFKGq_.rst
new file mode 100644
index 0000000000000..03ea46c3a83f1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-12-30-21-38-51.bpo-46195.jFKGq_.rst
@@ -0,0 +1,3 @@
+:func:`typing.get_type_hints` no longer adds ``Optional`` to parameters with
+``None`` as a default. This aligns to changes to PEP 484 in
+https://github.com/python/peps/pull/689



More information about the Python-checkins mailing list