mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-46195: Do not add Optional
in get_type_hints
(GH-30304)
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
parent
6ddb09f35b
commit
20a1c8ee4b
4 changed files with 26 additions and 34 deletions
|
@ -2185,9 +2185,7 @@ Introspection helpers
|
||||||
|
|
||||||
This is often the same as ``obj.__annotations__``. In addition,
|
This is often the same as ``obj.__annotations__``. In addition,
|
||||||
forward references encoded as string literals are handled by evaluating
|
forward references encoded as string literals are handled by evaluating
|
||||||
them in ``globals`` and ``locals`` namespaces. If necessary,
|
them in ``globals`` and ``locals`` namespaces. For a class ``C``, return
|
||||||
``Optional[t]`` is added for function and method annotations if a default
|
|
||||||
value equal to ``None`` is set. For a class ``C``, return
|
|
||||||
a dictionary constructed by merging all the ``__annotations__`` along
|
a dictionary constructed by merging all the ``__annotations__`` along
|
||||||
``C.__mro__`` in reverse order.
|
``C.__mro__`` in reverse order.
|
||||||
|
|
||||||
|
@ -2214,6 +2212,11 @@ Introspection helpers
|
||||||
.. versionchanged:: 3.9
|
.. versionchanged:: 3.9
|
||||||
Added ``include_extras`` parameter as part of :pep:`593`.
|
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_args(tp)
|
||||||
.. function:: get_origin(tp)
|
.. function:: get_origin(tp)
|
||||||
|
|
||||||
|
|
|
@ -2828,16 +2828,15 @@ class ForwardRefTests(BaseTestCase):
|
||||||
t = Node[int]
|
t = Node[int]
|
||||||
both_hints = get_type_hints(t.add_both, globals(), locals())
|
both_hints = get_type_hints(t.add_both, globals(), locals())
|
||||||
self.assertEqual(both_hints['left'], Optional[Node[T]])
|
self.assertEqual(both_hints['left'], Optional[Node[T]])
|
||||||
self.assertEqual(both_hints['right'], Optional[Node[T]])
|
self.assertEqual(both_hints['right'], Node[T])
|
||||||
self.assertEqual(both_hints['left'], both_hints['right'])
|
self.assertEqual(both_hints['stuff'], int)
|
||||||
self.assertEqual(both_hints['stuff'], Optional[int])
|
|
||||||
self.assertNotIn('blah', both_hints)
|
self.assertNotIn('blah', both_hints)
|
||||||
|
|
||||||
left_hints = get_type_hints(t.add_left, globals(), locals())
|
left_hints = get_type_hints(t.add_left, globals(), locals())
|
||||||
self.assertEqual(left_hints['node'], Optional[Node[T]])
|
self.assertEqual(left_hints['node'], Optional[Node[T]])
|
||||||
|
|
||||||
right_hints = get_type_hints(t.add_right, globals(), locals())
|
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):
|
def test_forwardref_instance_type_error(self):
|
||||||
fr = typing.ForwardRef('int')
|
fr = typing.ForwardRef('int')
|
||||||
|
@ -3630,6 +3629,18 @@ class GetTypeHintTests(BaseTestCase):
|
||||||
{'other': MySet[T], 'return': 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):
|
def test_get_type_hints_classes_str_annotations(self):
|
||||||
class Foo:
|
class Foo:
|
||||||
y = str
|
y = str
|
||||||
|
|
|
@ -1879,26 +1879,6 @@ def cast(typ, val):
|
||||||
return 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,
|
_allowed_types = (types.FunctionType, types.BuiltinFunctionType,
|
||||||
types.MethodType, types.ModuleType,
|
types.MethodType, types.ModuleType,
|
||||||
WrapperDescriptorType, MethodWrapperType, MethodDescriptorType)
|
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.
|
"""Return type hints for an object.
|
||||||
|
|
||||||
This is often the same as obj.__annotations__, but it handles
|
This is often the same as obj.__annotations__, but it handles
|
||||||
forward references encoded as string literals, adds Optional[t] if a
|
forward references encoded as string literals and recursively replaces all
|
||||||
default value equal to None is set and recursively replaces all
|
|
||||||
'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
|
'Annotated[T, ...]' with 'T' (unless 'include_extras=True').
|
||||||
|
|
||||||
The argument may be a module, class, method, or function. The annotations
|
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:
|
else:
|
||||||
raise TypeError('{!r} is not a module, class, method, '
|
raise TypeError('{!r} is not a module, class, method, '
|
||||||
'or function.'.format(obj))
|
'or function.'.format(obj))
|
||||||
defaults = _get_defaults(obj)
|
|
||||||
hints = dict(hints)
|
hints = dict(hints)
|
||||||
for name, value in hints.items():
|
for name, value in hints.items():
|
||||||
if value is None:
|
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_argument=not isinstance(obj, types.ModuleType),
|
||||||
is_class=False,
|
is_class=False,
|
||||||
)
|
)
|
||||||
value = _eval_type(value, globalns, localns)
|
hints[name] = _eval_type(value, globalns, localns)
|
||||||
if name in defaults and defaults[name] is None:
|
|
||||||
value = Optional[value]
|
|
||||||
hints[name] = value
|
|
||||||
return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
|
return hints if include_extras else {k: _strip_annotations(t) for k, t in hints.items()}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue