Skip to content

bpo-39990: try resolving type hints in pydoc #19874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
10 changes: 8 additions & 2 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ The Signature object represents the call signature of a callable object and its
return annotation. To retrieve a Signature object, use the :func:`signature`
function.

.. function:: signature(callable, \*, follow_wrapped=True)
.. function:: signature(callable, \*, follow_wrapped=True, resolve_type_hints=False)

Return a :class:`Signature` object for the given ``callable``::

Expand Down Expand Up @@ -588,6 +588,9 @@ function.
to it are positional-only. For more info, see
:ref:`the FAQ entry on positional-only parameters <faq-positional-only-arguments>`.

.. versionadded:: 3.9
``resolve_type_hints`` parameter. Pass ``True`` to resolve the type annotations.

.. versionadded:: 3.5
``follow_wrapped`` parameter. Pass ``False`` to get a signature of
``callable`` specifically (``callable.__wrapped__`` will not be used to
Expand Down Expand Up @@ -671,7 +674,7 @@ function.
>>> str(new_sig)
"(a, b) -> 'new return anno'"

.. classmethod:: Signature.from_callable(obj, \*, follow_wrapped=True)
.. classmethod:: Signature.from_callable(obj, \*, follow_wrapped=True, resolve_type_hints=False)

Return a :class:`Signature` (or its subclass) object for a given callable
``obj``. Pass ``follow_wrapped=False`` to get a signature of ``obj``
Expand All @@ -684,6 +687,9 @@ function.
sig = MySignature.from_callable(min)
assert isinstance(sig, MySignature)

.. versionadded:: 3.9
``resolve_type_hints`` parameter. Pass ``True`` to resolve the type annotations.

.. versionadded:: 3.5


Expand Down
31 changes: 24 additions & 7 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2136,7 +2136,7 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
return _signature_fromstr(cls, func, s, skip_bound_arg)


def _signature_from_function(cls, func, skip_bound_arg=True):
def _signature_from_function(cls, func, skip_bound_arg=True, resolve_type_hints=False):
"""Private helper: constructs Signature for the given python function."""

is_duck_function = False
Expand All @@ -2162,10 +2162,15 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
positional = arg_names[:pos_count]
keyword_only_count = func_code.co_kwonlyargcount
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
annotations = func.__annotations__
defaults = func.__defaults__
kwdefaults = func.__kwdefaults__

if resolve_type_hints:
import typing
annotations = typing.get_type_hints(func)
else:
annotations = func.__annotations__

if defaults:
pos_default_count = len(defaults)
else:
Expand Down Expand Up @@ -2233,6 +2238,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
def _signature_from_callable(obj, *,
follow_wrapper_chains=True,
skip_bound_arg=True,
resolve_type_hints=False,
sigcls):

"""Private helper function to get signature for arbitrary
Expand All @@ -2249,6 +2255,7 @@ def _signature_from_callable(obj, *,
obj.__func__,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
resolve_type_hints=resolve_type_hints,
sigcls=sigcls)

if skip_bound_arg:
Expand All @@ -2267,6 +2274,7 @@ def _signature_from_callable(obj, *,
obj,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
resolve_type_hints=resolve_type_hints,
sigcls=sigcls)

try:
Expand Down Expand Up @@ -2298,6 +2306,7 @@ def _signature_from_callable(obj, *,
partialmethod.func,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
resolve_type_hints=resolve_type_hints,
sigcls=sigcls)

sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
Expand All @@ -2317,7 +2326,8 @@ def _signature_from_callable(obj, *,
# If it's a pure Python function, or an object that is duck type
# of a Python function (Cython functions, for instance), then:
return _signature_from_function(sigcls, obj,
skip_bound_arg=skip_bound_arg)
skip_bound_arg=skip_bound_arg,
resolve_type_hints=resolve_type_hints)

if _signature_is_builtin(obj):
return _signature_from_builtin(sigcls, obj,
Expand All @@ -2328,6 +2338,7 @@ def _signature_from_callable(obj, *,
obj.func,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
resolve_type_hints=resolve_type_hints,
sigcls=sigcls)
return _signature_get_partial(wrapped_sig, obj)

Expand All @@ -2343,6 +2354,7 @@ def _signature_from_callable(obj, *,
call,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
resolve_type_hints=resolve_type_hints,
sigcls=sigcls)
else:
# Now we check if the 'obj' class has a '__new__' method
Expand All @@ -2352,6 +2364,7 @@ def _signature_from_callable(obj, *,
new,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
resolve_type_hints=resolve_type_hints,
sigcls=sigcls)
else:
# Finally, we should have at least __init__ implemented
Expand All @@ -2361,6 +2374,7 @@ def _signature_from_callable(obj, *,
init,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
resolve_type_hints=resolve_type_hints,
sigcls=sigcls)

if sig is None:
Expand Down Expand Up @@ -2411,6 +2425,7 @@ def _signature_from_callable(obj, *,
call,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
resolve_type_hints=resolve_type_hints,
sigcls=sigcls)
except ValueError as ex:
msg = 'no signature found for {!r}'.format(obj)
Expand Down Expand Up @@ -2863,10 +2878,11 @@ def from_builtin(cls, func):
return _signature_from_builtin(cls, func)

@classmethod
def from_callable(cls, obj, *, follow_wrapped=True):
def from_callable(cls, obj, *, follow_wrapped=True, resolve_type_hints=False):
"""Constructs Signature for the given callable object."""
return _signature_from_callable(obj, sigcls=cls,
follow_wrapper_chains=follow_wrapped)
follow_wrapper_chains=follow_wrapped,
resolve_type_hints=resolve_type_hints)

@property
def parameters(self):
Expand Down Expand Up @@ -3114,9 +3130,10 @@ def __str__(self):
return rendered


def signature(obj, *, follow_wrapped=True):
def signature(obj, *, follow_wrapped=True, resolve_type_hints=False):
"""Get a signature object for the passed callable."""
return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
resolve_type_hints=resolve_type_hints)


def _main():
Expand Down
36 changes: 24 additions & 12 deletions Lib/pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -907,9 +907,12 @@ def spilldata(msg, attrs, predicate):

decl = ''
try:
signature = inspect.signature(object)
except (ValueError, TypeError):
signature = None
signature = inspect.signature(object, resolve_type_hints=True)
except (NameError, AttributeError, SyntaxError, TypeError, ValueError):
try:
signature = inspect.signature(object, resolve_type_hints=False)
except (ValueError, TypeError):
signature = None
if signature:
argspec = str(signature)
if argspec and argspec != '()':
Expand Down Expand Up @@ -967,9 +970,12 @@ def docroutine(self, object, name=None, mod=None,
argspec = None
if inspect.isroutine(object):
try:
signature = inspect.signature(object)
except (ValueError, TypeError):
signature = None
signature = inspect.signature(object, resolve_type_hints=True)
except (NameError, AttributeError, SyntaxError, TypeError, ValueError):
try:
signature = inspect.signature(object, resolve_type_hints=False)
except (ValueError, TypeError):
signature = None
if signature:
argspec = str(signature)
if realname == '<lambda>':
Expand Down Expand Up @@ -1226,9 +1232,12 @@ def makename(c, m=object.__module__):
push = contents.append

try:
signature = inspect.signature(object)
except (ValueError, TypeError):
signature = None
signature = inspect.signature(object, resolve_type_hints=True)
except (NameError, AttributeError, SyntaxError, TypeError, ValueError):
try:
signature = inspect.signature(object, resolve_type_hints=False)
except (ValueError, TypeError):
signature = None
if signature:
argspec = str(signature)
if argspec and argspec != '()':
Expand Down Expand Up @@ -1397,9 +1406,12 @@ def docroutine(self, object, name=None, mod=None, cl=None):

if inspect.isroutine(object):
try:
signature = inspect.signature(object)
except (ValueError, TypeError):
signature = None
signature = inspect.signature(object, resolve_type_hints=True)
except (NameError, AttributeError, SyntaxError, TypeError, ValueError):
try:
signature = inspect.signature(object, resolve_type_hints=False)
except (ValueError, TypeError):
signature = None
if signature:
argspec = str(signature)
if realname == '<lambda>':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add option to resolve annotation strings when creating a inspect.Signature from function/callable.