[Python-checkins] bpo-40897:Give priority to using the current class constructor in `inspect.signature` (GH-27177) (GH-27209)

ambv webhook-mailer at python.org
Sat Jul 17 04:36:40 EDT 2021


https://github.com/python/cpython/commit/df7c62980d15acd3125dfbd81546dad359f7add7
commit: df7c62980d15acd3125dfbd81546dad359f7add7
branch: 3.9
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: ambv <lukasz at langa.pl>
date: 2021-07-17T10:36:31+02:00
summary:

bpo-40897:Give priority to using the current class constructor in `inspect.signature` (GH-27177) (GH-27209)

Co-authored-by: Łukasz Langa <lukasz at langa.pl>
(cherry picked from commit 6aab5f9bf303a8e4cd8377fabcdcb499e0541f9a)

Co-authored-by: Weipeng Hong <hongweichen8888 at sina.com>

files:
A Misc/NEWS.d/next/Library/2021-07-16-13-40-31.bpo-40897.aveAre.rst
M Lib/inspect.py
M Lib/test/test_inspect.py

diff --git a/Lib/inspect.py b/Lib/inspect.py
index 0d24b4f599491..9d1d46dce9496 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 d9e26a3033667..dc7f5c2427104 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -2958,6 +2958,47 @@ def __init__(self, b):
                            ('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 0000000000000..04f1465f0ac67
--- /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.



More information about the Python-checkins mailing list