From df7c62980d15acd3125dfbd81546dad359f7add7 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Sat, 17 Jul 2021 01:36:31 -0700 Subject: [PATCH] bpo-40897:Give priority to using the current class constructor in `inspect.signature` (GH-27177) (GH-27209) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Co-authored-by: Łukasz Langa (cherry picked from commit 6aab5f9bf303a8e4cd8377fabcdcb499e0541f9a) Co-authored-by: Weipeng Hong --- Lib/inspect.py | 24 +++++++---- Lib/test/test_inspect.py | 41 +++++++++++++++++++ .../2021-07-16-13-40-31.bpo-40897.aveAre.rst | 2 + 3 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-07-16-13-40-31.bpo-40897.aveAre.rst 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. -- 2.47.3