Skip to content
Open
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
54 changes: 28 additions & 26 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2486,29 +2486,11 @@ def _signature_from_callable(obj, *,
else:
return sig

# Was this function wrapped by a decorator?
if follow_wrapper_chains:
# Unwrap until we find an explicit signature or a MethodType (which will be
# handled explicitly below).
obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__")
or isinstance(f, types.MethodType)))
if isinstance(obj, types.MethodType):
# If the unwrapped object is a *method*, we might want to
# skip its first parameter (self).
# See test_signature_wrapped_bound_method for details.
return _get_signature_of(obj)

try:
sig = obj.__signature__
except AttributeError:
pass
else:
if sig is not None:
if not isinstance(sig, Signature):
raise TypeError(
'unexpected object {!r} in __signature__ '
'attribute'.format(sig))
return sig
# See issue 44337, handle functools.partial and functools.partialmethod
# before follow_wrapper_chains.
if isinstance(obj, functools.partial):
wrapped_sig = _get_signature_of(obj.func)
return _signature_get_partial(wrapped_sig, obj)

try:
partialmethod = obj.__partialmethod__
Expand Down Expand Up @@ -2543,9 +2525,29 @@ def _signature_from_callable(obj, *,
new_params = (first_wrapped_param,) + sig_params
return sig.replace(parameters=new_params)

if isinstance(obj, functools.partial):
wrapped_sig = _get_signature_of(obj.func)
return _signature_get_partial(wrapped_sig, obj)
# Was this function wrapped by a decorator?
if follow_wrapper_chains:
# Unwrap until we find an explicit signature or a MethodType (which will be
# handled explicitly below).
obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__")
or isinstance(f, types.MethodType)))
if isinstance(obj, types.MethodType):
# If the unwrapped object is a *method*, we might want to
# skip its first parameter (self).
# See test_signature_wrapped_bound_method for details.
return _get_signature_of(obj)

try:
sig = obj.__signature__
except AttributeError:
pass
else:
if sig is not None:
if not isinstance(sig, Signature):
raise TypeError(
'unexpected object {!r} in __signature__ '
'attribute'.format(sig))
return sig

if isfunction(obj) or _signature_is_functionlike(obj):
# If it's a pure Python function, or an object that is duck type
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -4087,6 +4087,30 @@ def foo(a): pass
foo.__partialmethod__ = 'spam'
self.assertEqual(str(inspect.signature(foo)), '(a)')

def test_signature_on_partial_unchange_in_update_wrapper(self):
from functools import partial, update_wrapper

def test(a, b, c, d):
pass
p = partial(test, a=1)
sig_partial = inspect.signature(p)
update_wrapper(p, test)
sig_updated = inspect.signature(p)
self.assertEqual(sig_updated, sig_partial)

def test_signature_on_partial_method_unchange_in_update_wrapper(self):
from functools import partialmethod, update_wrapper

class Spam:
def test(it, a, b, c, d) -> 'spam':
pass
ham = partialmethod(test, a=1)

sig_partial = inspect.signature(Spam.ham)
update_wrapper(Spam.ham, Spam.test)
sig_updated = inspect.signature(Spam.ham)
self.assertEqual(sig_updated, sig_partial)

def test_signature_on_decorated(self):
def decorator(func):
@functools.wraps(func)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make :func:`inspect.signature()` returns correct signature after :func:`functools.update_wrapper()` is called. Patch by Ofey Chan.
Loading