gh-119605: Respect follow_wrapped for __init__ and __new__ when getting class signature with inspect.signature (#132055)

This commit is contained in:
Xuehai Pan 2025-05-02 06:41:44 +08:00 committed by GitHub
parent c14134020f
commit b8633f9aca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 136 additions and 8 deletions

View file

@ -1901,7 +1901,7 @@ _NonUserDefinedCallables = (types.WrapperDescriptorType,
types.BuiltinFunctionType)
def _signature_get_user_defined_method(cls, method_name):
def _signature_get_user_defined_method(cls, method_name, *, follow_wrapper_chains=True):
"""Private helper. Checks if ``cls`` has an attribute
named ``method_name`` and returns it only if it is a
pure python function.
@ -1910,12 +1910,20 @@ def _signature_get_user_defined_method(cls, method_name):
meth = getattr(cls, method_name, None)
else:
meth = getattr_static(cls, method_name, None)
if meth is None or isinstance(meth, _NonUserDefinedCallables):
if meth is None:
return None
if follow_wrapper_chains:
meth = unwrap(meth, stop=(lambda m: hasattr(m, "__signature__")
or _signature_is_builtin(m)))
if isinstance(meth, _NonUserDefinedCallables):
# Once '__signature__' will be added to 'C'-level
# callables, this check won't be necessary
return None
if method_name != '__new__':
meth = _descriptor_get(meth, cls)
if follow_wrapper_chains:
meth = unwrap(meth, stop=lambda m: hasattr(m, "__signature__"))
return meth
@ -2507,12 +2515,26 @@ def _signature_from_callable(obj, *,
# First, let's see if it has an overloaded __call__ defined
# in its metaclass
call = _signature_get_user_defined_method(type(obj), '__call__')
call = _signature_get_user_defined_method(
type(obj),
'__call__',
follow_wrapper_chains=follow_wrapper_chains,
)
if call is not None:
return _get_signature_of(call)
new = _signature_get_user_defined_method(obj, '__new__')
init = _signature_get_user_defined_method(obj, '__init__')
# NOTE: The user-defined method can be a function with a thin wrapper
# around object.__new__ (e.g., generated by `@warnings.deprecated`)
new = _signature_get_user_defined_method(
obj,
'__new__',
follow_wrapper_chains=follow_wrapper_chains,
)
init = _signature_get_user_defined_method(
obj,
'__init__',
follow_wrapper_chains=follow_wrapper_chains,
)
# Go through the MRO and see if any class has user-defined
# pure Python __new__ or __init__ method
@ -2552,10 +2574,14 @@ def _signature_from_callable(obj, *,
# Last option is to check if its '__init__' is
# object.__init__ or type.__init__.
if type not in obj.__mro__:
obj_init = obj.__init__
obj_new = obj.__new__
if follow_wrapper_chains:
obj_init = unwrap(obj_init)
obj_new = unwrap(obj_new)
# We have a class (not metaclass), but no user-defined
# __init__ or __new__ for it
if (obj.__init__ is object.__init__ and
obj.__new__ is object.__new__):
if obj_init is object.__init__ and obj_new is object.__new__:
# Return a signature of 'object' builtin.
return sigcls.from_callable(object)
else: