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 726c117

Browse filesBrowse files
[3.11] gh-101293: Fix support of custom callables and types in inspect.Signature.from_callable() (GH-115530) (GH-116197)
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 <storchaka@gmail.com>
1 parent 8813e5a commit 726c117
Copy full SHA for 726c117

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
@@ -1945,15 +1945,17 @@ def _signature_get_user_defined_method(cls, method_name):
19451945
named ``method_name`` and returns it only if it is a
19461946
pure python function.
19471947
"""
1948-
try:
1949-
meth = getattr(cls, method_name)
1950-
except AttributeError:
1951-
return
1948+
if method_name == '__new__':
1949+
meth = getattr(cls, method_name, None)
19521950
else:
1953-
if not isinstance(meth, _NonUserDefinedCallables):
1954-
# Once '__signature__' will be added to 'C'-level
1955-
# callables, this check won't be necessary
1956-
return meth
1951+
meth = getattr_static(cls, method_name, None)
1952+
if meth is None or isinstance(meth, _NonUserDefinedCallables):
1953+
# Once '__signature__' will be added to 'C'-level
1954+
# callables, this check won't be necessary
1955+
return None
1956+
if method_name != '__new__':
1957+
meth = _descriptor_get(meth, cls)
1958+
return meth
19571959

19581960

19591961
def _signature_get_partial(wrapped_sig, partial, extra_args=()):
@@ -2421,6 +2423,15 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
24212423
__validate_parameters__=is_duck_function)
24222424

24232425

2426+
def _descriptor_get(descriptor, obj):
2427+
if isclass(descriptor):
2428+
return descriptor
2429+
get = getattr(type(descriptor), '__get__', _sentinel)
2430+
if get is _sentinel:
2431+
return descriptor
2432+
return get(descriptor, obj, type(obj))
2433+
2434+
24242435
def _signature_from_callable(obj, *,
24252436
follow_wrapper_chains=True,
24262437
skip_bound_arg=True,
@@ -2521,96 +2532,72 @@ def _signature_from_callable(obj, *,
25212532
wrapped_sig = _get_signature_of(obj.func)
25222533
return _signature_get_partial(wrapped_sig, obj)
25232534

2524-
sig = None
25252535
if isinstance(obj, type):
25262536
# obj is a class or a metaclass
25272537

25282538
# First, let's see if it has an overloaded __call__ defined
25292539
# in its metaclass
25302540
call = _signature_get_user_defined_method(type(obj), '__call__')
25312541
if call is not None:
2532-
sig = _get_signature_of(call)
2533-
else:
2534-
factory_method = None
2535-
new = _signature_get_user_defined_method(obj, '__new__')
2536-
init = _signature_get_user_defined_method(obj, '__init__')
2537-
2538-
# Go through the MRO and see if any class has user-defined
2539-
# pure Python __new__ or __init__ method
2540-
for base in obj.__mro__:
2541-
# Now we check if the 'obj' class has an own '__new__' method
2542-
if new is not None and '__new__' in base.__dict__:
2543-
factory_method = new
2544-
break
2545-
# or an own '__init__' method
2546-
elif init is not None and '__init__' in base.__dict__:
2547-
factory_method = init
2548-
break
2542+
return _get_signature_of(call)
25492543

2550-
if factory_method is not None:
2551-
sig = _get_signature_of(factory_method)
2552-
2553-
if sig is None:
2554-
# At this point we know, that `obj` is a class, with no user-
2555-
# defined '__init__', '__new__', or class-level '__call__'
2556-
2557-
for base in obj.__mro__[:-1]:
2558-
# Since '__text_signature__' is implemented as a
2559-
# descriptor that extracts text signature from the
2560-
# class docstring, if 'obj' is derived from a builtin
2561-
# class, its own '__text_signature__' may be 'None'.
2562-
# Therefore, we go through the MRO (except the last
2563-
# class in there, which is 'object') to find the first
2564-
# class with non-empty text signature.
2565-
try:
2566-
text_sig = base.__text_signature__
2567-
except AttributeError:
2568-
pass
2569-
else:
2570-
if text_sig:
2571-
# If 'base' class has a __text_signature__ attribute:
2572-
# return a signature based on it
2573-
return _signature_fromstr(sigcls, base, text_sig)
2574-
2575-
# No '__text_signature__' was found for the 'obj' class.
2576-
# Last option is to check if its '__init__' is
2577-
# object.__init__ or type.__init__.
2578-
if type not in obj.__mro__:
2579-
# We have a class (not metaclass), but no user-defined
2580-
# __init__ or __new__ for it
2581-
if (obj.__init__ is object.__init__ and
2582-
obj.__new__ is object.__new__):
2583-
# Return a signature of 'object' builtin.
2584-
return sigcls.from_callable(object)
2585-
else:
2586-
raise ValueError(
2587-
'no signature found for builtin type {!r}'.format(obj))
2544+
new = _signature_get_user_defined_method(obj, '__new__')
2545+
init = _signature_get_user_defined_method(obj, '__init__')
25882546

2589-
elif not isinstance(obj, _NonUserDefinedCallables):
2590-
# An object with __call__
2591-
# We also check that the 'obj' is not an instance of
2592-
# types.WrapperDescriptorType or types.MethodWrapperType to avoid
2593-
# infinite recursion (and even potential segfault)
2594-
call = _signature_get_user_defined_method(type(obj), '__call__')
2595-
if call is not None:
2547+
# Go through the MRO and see if any class has user-defined
2548+
# pure Python __new__ or __init__ method
2549+
for base in obj.__mro__:
2550+
# Now we check if the 'obj' class has an own '__new__' method
2551+
if new is not None and '__new__' in base.__dict__:
2552+
sig = _get_signature_of(new)
2553+
if skip_bound_arg:
2554+
sig = _signature_bound_method(sig)
2555+
return sig
2556+
# or an own '__init__' method
2557+
elif init is not None and '__init__' in base.__dict__:
2558+
return _get_signature_of(init)
2559+
2560+
# At this point we know, that `obj` is a class, with no user-
2561+
# defined '__init__', '__new__', or class-level '__call__'
2562+
2563+
for base in obj.__mro__[:-1]:
2564+
# Since '__text_signature__' is implemented as a
2565+
# descriptor that extracts text signature from the
2566+
# class docstring, if 'obj' is derived from a builtin
2567+
# class, its own '__text_signature__' may be 'None'.
2568+
# Therefore, we go through the MRO (except the last
2569+
# class in there, which is 'object') to find the first
2570+
# class with non-empty text signature.
25962571
try:
2597-
sig = _get_signature_of(call)
2598-
except ValueError as ex:
2599-
msg = 'no signature found for {!r}'.format(obj)
2600-
raise ValueError(msg) from ex
2601-
2602-
if sig is not None:
2603-
# For classes and objects we skip the first parameter of their
2604-
# __call__, __new__, or __init__ methods
2605-
if skip_bound_arg:
2606-
return _signature_bound_method(sig)
2607-
else:
2608-
return sig
2572+
text_sig = base.__text_signature__
2573+
except AttributeError:
2574+
pass
2575+
else:
2576+
if text_sig:
2577+
# If 'base' class has a __text_signature__ attribute:
2578+
# return a signature based on it
2579+
return _signature_fromstr(sigcls, base, text_sig)
2580+
2581+
# No '__text_signature__' was found for the 'obj' class.
2582+
# Last option is to check if its '__init__' is
2583+
# object.__init__ or type.__init__.
2584+
if type not in obj.__mro__:
2585+
# We have a class (not metaclass), but no user-defined
2586+
# __init__ or __new__ for it
2587+
if (obj.__init__ is object.__init__ and
2588+
obj.__new__ is object.__new__):
2589+
# Return a signature of 'object' builtin.
2590+
return sigcls.from_callable(object)
2591+
else:
2592+
raise ValueError(
2593+
'no signature found for builtin type {!r}'.format(obj))
26092594

2610-
if isinstance(obj, types.BuiltinFunctionType):
2611-
# Raise a nicer error message for builtins
2612-
msg = 'no signature found for builtin function {!r}'.format(obj)
2613-
raise ValueError(msg)
2595+
else:
2596+
# An object with __call__
2597+
call = getattr_static(type(obj), '__call__', None)
2598+
if call is not None:
2599+
call = _descriptor_get(call, obj)
2600+
return _get_signature_of(call)
26142601

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

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.