From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Sat, 17 Jul 2021 08:36:31 +0000 (-0700) Subject: bpo-40897:Give priority to using the current class constructor in `inspect.signature... X-Git-Tag: v3.9.7~152 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=df7c62980d15acd3125dfbd81546dad359f7add7;p=thirdparty%2FPython%2Fcpython.git bpo-40897:Give priority to using the current class constructor in `inspect.signature` (GH-27177) (GH-27209) Co-authored-by: Ɓukasz Langa (cherry picked from commit 6aab5f9bf303a8e4cd8377fabcdcb499e0541f9a) Co-authored-by: Weipeng Hong --- diff --git a/Lib/inspect.py b/Lib/inspect.py index 0d24b4f59949..9d1d46dce949 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2341,15 +2341,23 @@ def _signature_from_callable(obj, *, if call is not None: sig = _get_signature_of(call) else: - # Now we check if the 'obj' class has a '__new__' method + factory_method = None new = _signature_get_user_defined_method(obj, '__new__') - if new is not None: - sig = _get_signature_of(new) - else: - # Finally, we should have at least __init__ implemented - init = _signature_get_user_defined_method(obj, '__init__') - if init is not None: - sig = _get_signature_of(init) + init = _signature_get_user_defined_method(obj, '__init__') + # Now we check if the 'obj' class has an own '__new__' method + if '__new__' in obj.__dict__: + factory_method = new + # or an own '__init__' method + elif '__init__' in obj.__dict__: + factory_method = init + # If not, we take inherited '__new__' or '__init__', if present + elif new is not None: + factory_method = new + elif init is not None: + factory_method = init + + if factory_method is not None: + sig = _get_signature_of(factory_method) if sig is None: # At this point we know, that `obj` is a class, with no user- diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index d9e26a303366..dc7f5c242710 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -2958,6 +2958,47 @@ class TestSignatureObject(unittest.TestCase): ('bar', 2, ..., "keyword_only")), ...)) + def test_signature_on_subclass(self): + class A: + def __new__(cls, a=1, *args, **kwargs): + return object.__new__(cls) + class B(A): + def __init__(self, b): + pass + class C(A): + def __new__(cls, a=1, b=2, *args, **kwargs): + return object.__new__(cls) + class D(A): + pass + + self.assertEqual(self.signature(B), + ((('b', ..., ..., "positional_or_keyword"),), + ...)) + self.assertEqual(self.signature(C), + ((('a', 1, ..., 'positional_or_keyword'), + ('b', 2, ..., 'positional_or_keyword'), + ('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + self.assertEqual(self.signature(D), + ((('a', 1, ..., 'positional_or_keyword'), + ('args', ..., ..., 'var_positional'), + ('kwargs', ..., ..., 'var_keyword')), + ...)) + + def test_signature_on_generic_subclass(self): + from typing import Generic, TypeVar + + T = TypeVar('T') + + class A(Generic[T]): + def __init__(self, *, a: int) -> None: + pass + + self.assertEqual(self.signature(A), + ((('a', ..., int, 'keyword_only'),), + None)) + @unittest.skipIf(MISSING_C_DOCSTRINGS, "Signature information for builtins requires docstrings") def test_signature_on_class_without_init(self): diff --git a/Misc/NEWS.d/next/Library/2021-07-16-13-40-31.bpo-40897.aveAre.rst b/Misc/NEWS.d/next/Library/2021-07-16-13-40-31.bpo-40897.aveAre.rst new file mode 100644 index 000000000000..04f1465f0ac6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-07-16-13-40-31.bpo-40897.aveAre.rst @@ -0,0 +1,2 @@ +Give priority to using the current class constructor in +:func:`inspect.signature`. Patch by Weipeng Hong.