Skip to content

Commit 59167c9

Browse files
pythongh-101293: Fix support of custom callables and types in inspect.Signature.from_callable() (pythonGH-115530)
Support callables with the __call__() method and types with __new__() and __init__() methods set to class methods, static methods, bound methods, partial functions, and other types of methods and descriptors. Add tests for numerous types of callables and descriptors.
1 parent 8ab6c27 commit 59167c9

File tree

3 files changed

+438
-89
lines changed

3 files changed

+438
-89
lines changed

Lib/inspect.py

Lines changed: 74 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2039,15 +2039,17 @@ def _signature_get_user_defined_method(cls, method_name):
20392039
named ``method_name`` and returns it only if it is a
20402040
pure python function.
20412041
"""
2042-
try:
2043-
meth = getattr(cls, method_name)
2044-
except AttributeError:
2045-
return
2042+
if method_name == '__new__':
2043+
meth = getattr(cls, method_name, None)
20462044
else:
2047-
if not isinstance(meth, _NonUserDefinedCallables):
2048-
# Once '__signature__' will be added to 'C'-level
2049-
# callables, this check won't be necessary
2050-
return meth
2045+
meth = getattr_static(cls, method_name, None)
2046+
if meth is None or isinstance(meth, _NonUserDefinedCallables):
2047+
# Once '__signature__' will be added to 'C'-level
2048+
# callables, this check won't be necessary
2049+
return None
2050+
if method_name != '__new__':
2051+
meth = _descriptor_get(meth, cls)
2052+
return meth
20512053

20522054

20532055
def _signature_get_partial(wrapped_sig, partial, extra_args=()):
@@ -2492,6 +2494,15 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
24922494
__validate_parameters__=is_duck_function)
24932495

24942496

2497+
def _descriptor_get(descriptor, obj):
2498+
if isclass(descriptor):
2499+
return descriptor
2500+
get = getattr(type(descriptor), '__get__', _sentinel)
2501+
if get is _sentinel:
2502+
return descriptor
2503+
return get(descriptor, obj, type(obj))
2504+
2505+
24952506
def _signature_from_callable(obj, *,
24962507
follow_wrapper_chains=True,
24972508
skip_bound_arg=True,
@@ -2600,96 +2611,72 @@ def _signature_from_callable(obj, *,
26002611
wrapped_sig = _get_signature_of(obj.func)
26012612
return _signature_get_partial(wrapped_sig, obj)
26022613

2603-
sig = None
26042614
if isinstance(obj, type):
26052615
# obj is a class or a metaclass
26062616

26072617
# First, let's see if it has an overloaded __call__ defined
26082618
# in its metaclass
26092619
call = _signature_get_user_defined_method(type(obj), '__call__')
26102620
if call is not None:
2611-
sig = _get_signature_of(call)
2612-
else:
2613-
factory_method = None
2614-
new = _signature_get_user_defined_method(obj, '__new__')
2615-
init = _signature_get_user_defined_method(obj, '__init__')
2616-
2617-
# Go through the MRO and see if any class has user-defined
2618-
# pure Python __new__ or __init__ method
2619-
for base in obj.__mro__:
2620-
# Now we check if the 'obj' class has an own '__new__' method
2621-
if new is not None and '__new__' in base.__dict__:
2622-
factory_method = new
2623-
break
2624-
# or an own '__init__' method
2625-
elif init is not None and '__init__' in base.__dict__:
2626-
factory_method = init
2627-
break
2621+
return _get_signature_of(call)
26282622

2629-
if factory_method is not None:
2630-
sig = _get_signature_of(factory_method)
2631-
2632-
if sig is None:
2633-
# At this point we know, that `obj` is a class, with no user-
2634-
# defined '__init__', '__new__', or class-level '__call__'
2635-
2636-
for base in obj.__mro__[:-1]:
2637-
# Since '__text_signature__' is implemented as a
2638-
# descriptor that extracts text signature from the
2639-
# class docstring, if 'obj' is derived from a builtin
2640-
# class, its own '__text_signature__' may be 'None'.
2641-
# Therefore, we go through the MRO (except the last
2642-
# class in there, which is 'object') to find the first
2643-
# class with non-empty text signature.
2644-
try:
2645-
text_sig = base.__text_signature__
2646-
except AttributeError:
2647-
pass
2648-
else:
2649-
if text_sig:
2650-
# If 'base' class has a __text_signature__ attribute:
2651-
# return a signature based on it
2652-
return _signature_fromstr(sigcls, base, text_sig)
2653-
2654-
# No '__text_signature__' was found for the 'obj' class.
2655-
# Last option is to check if its '__init__' is
2656-
# object.__init__ or type.__init__.
2657-
if type not in obj.__mro__:
2658-
# We have a class (not metaclass), but no user-defined
2659-
# __init__ or __new__ for it
2660-
if (obj.__init__ is object.__init__ and
2661-
obj.__new__ is object.__new__):
2662-
# Return a signature of 'object' builtin.
2663-
return sigcls.from_callable(object)
2664-
else:
2665-
raise ValueError(
2666-
'no signature found for builtin type {!r}'.format(obj))
2623+
new = _signature_get_user_defined_method(obj, '__new__')
2624+
init = _signature_get_user_defined_method(obj, '__init__')
26672625

2668-
elif not isinstance(obj, _NonUserDefinedCallables):
2669-
# An object with __call__
2670-
# We also check that the 'obj' is not an instance of
2671-
# types.WrapperDescriptorType or types.MethodWrapperType to avoid
2672-
# infinite recursion (and even potential segfault)
2673-
call = _signature_get_user_defined_method(type(obj), '__call__')
2674-
if call is not None:
2626+
# Go through the MRO and see if any class has user-defined
2627+
# pure Python __new__ or __init__ method
2628+
for base in obj.__mro__:
2629+
# Now we check if the 'obj' class has an own '__new__' method
2630+
if new is not None and '__new__' in base.__dict__:
2631+
sig = _get_signature_of(new)
2632+
if skip_bound_arg:
2633+
sig = _signature_bound_method(sig)
2634+
return sig
2635+
# or an own '__init__' method
2636+
elif init is not None and '__init__' in base.__dict__:
2637+
return _get_signature_of(init)
2638+
2639+
# At this point we know, that `obj` is a class, with no user-
2640+
# defined '__init__', '__new__', or class-level '__call__'
2641+
2642+
for base in obj.__mro__[:-1]:
2643+
# Since '__text_signature__' is implemented as a
2644+
# descriptor that extracts text signature from the
2645+
# class docstring, if 'obj' is derived from a builtin
2646+
# class, its own '__text_signature__' may be 'None'.
2647+
# Therefore, we go through the MRO (except the last
2648+
# class in there, which is 'object') to find the first
2649+
# class with non-empty text signature.
26752650
try:
2676-
sig = _get_signature_of(call)
2677-
except ValueError as ex:
2678-
msg = 'no signature found for {!r}'.format(obj)
2679-
raise ValueError(msg) from ex
2680-
2681-
if sig is not None:
2682-
# For classes and objects we skip the first parameter of their
2683-
# __call__, __new__, or __init__ methods
2684-
if skip_bound_arg:
2685-
return _signature_bound_method(sig)
2686-
else:
2687-
return sig
2651+
text_sig = base.__text_signature__
2652+
except AttributeError:
2653+
pass
2654+
else:
2655+
if text_sig:
2656+
# If 'base' class has a __text_signature__ attribute:
2657+
# return a signature based on it
2658+
return _signature_fromstr(sigcls, base, text_sig)
2659+
2660+
# No '__text_signature__' was found for the 'obj' class.
2661+
# Last option is to check if its '__init__' is
2662+
# object.__init__ or type.__init__.
2663+
if type not in obj.__mro__:
2664+
# We have a class (not metaclass), but no user-defined
2665+
# __init__ or __new__ for it
2666+
if (obj.__init__ is object.__init__ and
2667+
obj.__new__ is object.__new__):
2668+
# Return a signature of 'object' builtin.
2669+
return sigcls.from_callable(object)
2670+
else:
2671+
raise ValueError(
2672+
'no signature found for builtin type {!r}'.format(obj))
26882673

2689-
if isinstance(obj, types.BuiltinFunctionType):
2690-
# Raise a nicer error message for builtins
2691-
msg = 'no signature found for builtin function {!r}'.format(obj)
2692-
raise ValueError(msg)
2674+
else:
2675+
# An object with __call__
2676+
call = getattr_static(type(obj), '__call__', None)
2677+
if call is not None:
2678+
call = _descriptor_get(call, obj)
2679+
return _get_signature_of(call)
26932680

26942681
raise ValueError('callable {!r} is not supported by signature'.format(obj))
26952682

0 commit comments

Comments
 (0)