Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
9 changes: 6 additions & 3 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2030,9 +2030,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.

Expand All @@ -2059,6 +2057,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)

Expand Down
19 changes: 15 additions & 4 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2586,16 +2586,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')
Expand Down Expand Up @@ -3259,6 +3258,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
Expand Down
29 changes: 2 additions & 27 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1728,26 +1728,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)
Expand All @@ -1757,8 +1737,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
Expand Down Expand Up @@ -1838,7 +1817,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:
Expand All @@ -1851,10 +1829,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()}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Now :func:`typing.get_type_hints` does not add ``Optional`` to types with
explicit ``None`` default value. This aligns to changes to PEP 484 in
https://github.com/python/peps/pull/689