Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 59167c9

Browse filesBrowse files
gh-101293: Fix support of custom callables and types in inspect.Signature.from_callable() (GH-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
Copy full SHA for 59167c9

File tree

Expand file treeCollapse file tree

3 files changed

+438
-89
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+438
-89
lines changed

‎Lib/inspect.py

Copy file name to clipboardExpand all lines: Lib/inspect.py
+74-87Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.