Skip to content

Commit d341098

Browse files
[3.12] pythongh-101293: Fix support of custom callables and types in inspect.Signature.from_callable() (pythonGH-115530) (pythonGH-116198)
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. (cherry picked from commit 59167c9) Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 753b664 commit d341098

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
@@ -2003,15 +2003,17 @@ def _signature_get_user_defined_method(cls, method_name):
20032003
named ``method_name`` and returns it only if it is a
20042004
pure python function.
20052005
"""
2006-
try:
2007-
meth = getattr(cls, method_name)
2008-
except AttributeError:
2009-
return
2006+
if method_name == '__new__':
2007+
meth = getattr(cls, method_name, None)
20102008
else:
2011-
if not isinstance(meth, _NonUserDefinedCallables):
2012-
# Once '__signature__' will be added to 'C'-level
2013-
# callables, this check won't be necessary
2014-
return meth
2009+
meth = getattr_static(cls, method_name, None)
2010+
if meth is None or isinstance(meth, _NonUserDefinedCallables):
2011+
# Once '__signature__' will be added to 'C'-level
2012+
# callables, this check won't be necessary
2013+
return None
2014+
if method_name != '__new__':
2015+
meth = _descriptor_get(meth, cls)
2016+
return meth
20152017

20162018

20172019
def _signature_get_partial(wrapped_sig, partial, extra_args=()):
@@ -2456,6 +2458,15 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
24562458
__validate_parameters__=is_duck_function)
24572459

24582460

2461+
def _descriptor_get(descriptor, obj):
2462+
if isclass(descriptor):
2463+
return descriptor
2464+
get = getattr(type(descriptor), '__get__', _sentinel)
2465+
if get is _sentinel:
2466+
return descriptor
2467+
return get(descriptor, obj, type(obj))
2468+
2469+
24592470
def _signature_from_callable(obj, *,
24602471
follow_wrapper_chains=True,
24612472
skip_bound_arg=True,
@@ -2564,96 +2575,72 @@ def _signature_from_callable(obj, *,
25642575
wrapped_sig = _get_signature_of(obj.func)
25652576
return _signature_get_partial(wrapped_sig, obj)
25662577

2567-
sig = None
25682578
if isinstance(obj, type):
25692579
# obj is a class or a metaclass
25702580

25712581
# First, let's see if it has an overloaded __call__ defined
25722582
# in its metaclass
25732583
call = _signature_get_user_defined_method(type(obj), '__call__')
25742584
if call is not None:
2575-
sig = _get_signature_of(call)
2576-
else:
2577-
factory_method = None
2578-
new = _signature_get_user_defined_method(obj, '__new__')
2579-
init = _signature_get_user_defined_method(obj, '__init__')
2580-
2581-
# Go through the MRO and see if any class has user-defined
2582-
# pure Python __new__ or __init__ method
2583-
for base in obj.__mro__:
2584-
# Now we check if the 'obj' class has an own '__new__' method
2585-
if new is not None and '__new__' in base.__dict__:
2586-
factory_method = new
2587-
break
2588-
# or an own '__init__' method
2589-
elif init is not None and '__init__' in base.__dict__:
2590-
factory_method = init
2591-
break
2585+
return _get_signature_of(call)
25922586

2593-
if factory_method is not None:
2594-
sig = _get_signature_of(factory_method)
2595-
2596-
if sig is None:
2597-
# At this point we know, that `obj` is a class, with no user-
2598-
# defined '__init__', '__new__', or class-level '__call__'
2599-
2600-
for base in obj.__mro__[:-1]:
2601-
# Since '__text_signature__' is implemented as a
2602-
# descriptor that extracts text signature from the
2603-
# class docstring, if 'obj' is derived from a builtin
2604-
# class, its own '__text_signature__' may be 'None'.
2605-
# Therefore, we go through the MRO (except the last
2606-
# class in there, which is 'object') to find the first
2607-
# class with non-empty text signature.
2608-
try:
2609-
text_sig = base.__text_signature__
2610-
except AttributeError:
2611-
pass
2612-
else:
2613-
if text_sig:
2614-
# If 'base' class has a __text_signature__ attribute:
2615-
# return a signature based on it
2616-
return _signature_fromstr(sigcls, base, text_sig)
2617-
2618-
# No '__text_signature__' was found for the 'obj' class.
2619-
# Last option is to check if its '__init__' is
2620-
# object.__init__ or type.__init__.
2621-
if type not in obj.__mro__:
2622-
# We have a class (not metaclass), but no user-defined
2623-
# __init__ or __new__ for it
2624-
if (obj.__init__ is object.__init__ and
2625-
obj.__new__ is object.__new__):
2626-
# Return a signature of 'object' builtin.
2627-
return sigcls.from_callable(object)
2628-
else:
2629-
raise ValueError(
2630-
'no signature found for builtin type {!r}'.format(obj))
2587+
new = _signature_get_user_defined_method(obj, '__new__')
2588+
init = _signature_get_user_defined_method(obj, '__init__')
26312589

2632-
elif not isinstance(obj, _NonUserDefinedCallables):
2633-
# An object with __call__
2634-
# We also check that the 'obj' is not an instance of
2635-
# types.WrapperDescriptorType or types.MethodWrapperType to avoid
2636-
# infinite recursion (and even potential segfault)
2637-
call = _signature_get_user_defined_method(type(obj), '__call__')
2638-
if call is not None:
2590+
# Go through the MRO and see if any class has user-defined
2591+
# pure Python __new__ or __init__ method
2592+
for base in obj.__mro__:
2593+
# Now we check if the 'obj' class has an own '__new__' method
2594+
if new is not None and '__new__' in base.__dict__:
2595+
sig = _get_signature_of(new)
2596+
if skip_bound_arg:
2597+
sig = _signature_bound_method(sig)
2598+
return sig
2599+
# or an own '__init__' method
2600+
elif init is not None and '__init__' in base.__dict__:
2601+
return _get_signature_of(init)
2602+
2603+
# At this point we know, that `obj` is a class, with no user-
2604+
# defined '__init__', '__new__', or class-level '__call__'
2605+
2606+
for base in obj.__mro__[:-1]:
2607+
# Since '__text_signature__' is implemented as a
2608+
# descriptor that extracts text signature from the
2609+
# class docstring, if 'obj' is derived from a builtin
2610+
# class, its own '__text_signature__' may be 'None'.
2611+
# Therefore, we go through the MRO (except the last
2612+
# class in there, which is 'object') to find the first
2613+
# class with non-empty text signature.
26392614
try:
2640-
sig = _get_signature_of(call)
2641-
except ValueError as ex:
2642-
msg = 'no signature found for {!r}'.format(obj)
2643-
raise ValueError(msg) from ex
2644-
2645-
if sig is not None:
2646-
# For classes and objects we skip the first parameter of their
2647-
# __call__, __new__, or __init__ methods
2648-
if skip_bound_arg:
2649-
return _signature_bound_method(sig)
2650-
else:
2651-
return sig
2615+
text_sig = base.__text_signature__
2616+
except AttributeError:
2617+
pass
2618+
else:
2619+
if text_sig:
2620+
# If 'base' class has a __text_signature__ attribute:
2621+
# return a signature based on it
2622+
return _signature_fromstr(sigcls, base, text_sig)
2623+
2624+
# No '__text_signature__' was found for the 'obj' class.
2625+
# Last option is to check if its '__init__' is
2626+
# object.__init__ or type.__init__.
2627+
if type not in obj.__mro__:
2628+
# We have a class (not metaclass), but no user-defined
2629+
# __init__ or __new__ for it
2630+
if (obj.__init__ is object.__init__ and
2631+
obj.__new__ is object.__new__):
2632+
# Return a signature of 'object' builtin.
2633+
return sigcls.from_callable(object)
2634+
else:
2635+
raise ValueError(
2636+
'no signature found for builtin type {!r}'.format(obj))
26522637

2653-
if isinstance(obj, types.BuiltinFunctionType):
2654-
# Raise a nicer error message for builtins
2655-
msg = 'no signature found for builtin function {!r}'.format(obj)
2656-
raise ValueError(msg)
2638+
else:
2639+
# An object with __call__
2640+
call = getattr_static(type(obj), '__call__', None)
2641+
if call is not None:
2642+
call = _descriptor_get(call, obj)
2643+
return _get_signature_of(call)
26572644

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

0 commit comments

Comments
 (0)