From 948e39a866ccf33b4e30668c3f88a95a65966159 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Fri, 16 Jul 2021 06:25:57 -0700 Subject: bpo-40897:Give priority to using the current class constructor in `inspect.signature` (GH-27177) (#27189) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 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 0273ffa..750fd45 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2471,15 +2471,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 0ab6530..38d1618 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -3063,6 +3063,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 0000000..04f1465 --- /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. -- cgit v0.12