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

Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
Weipeng Hong 2021-07-16 21:04:27 +08:00 committed by GitHub
parent 8ce3008585
commit 6aab5f9bf3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 8 deletions

View file

@ -2444,15 +2444,23 @@ def _signature_from_callable(obj, *,
if call is not None: if call is not None:
sig = _get_signature_of(call) sig = _get_signature_of(call)
else: else:
# Now we check if the 'obj' class has a '__new__' method factory_method = None
new = _signature_get_user_defined_method(obj, '__new__') new = _signature_get_user_defined_method(obj, '__new__')
if new is not None: init = _signature_get_user_defined_method(obj, '__init__')
sig = _get_signature_of(new) # Now we check if the 'obj' class has an own '__new__' method
else: if '__new__' in obj.__dict__:
# Finally, we should have at least __init__ implemented factory_method = new
init = _signature_get_user_defined_method(obj, '__init__') # or an own '__init__' method
if init is not None: elif '__init__' in obj.__dict__:
sig = _get_signature_of(init) 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: if sig is None:
# At this point we know, that `obj` is a class, with no user- # At this point we know, that `obj` is a class, with no user-

View file

@ -3063,6 +3063,47 @@ class TestSignatureObject(unittest.TestCase):
('bar', 2, ..., "keyword_only")), ('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, @unittest.skipIf(MISSING_C_DOCSTRINGS,
"Signature information for builtins requires docstrings") "Signature information for builtins requires docstrings")
def test_signature_on_class_without_init(self): def test_signature_on_class_without_init(self):

View file

@ -0,0 +1,2 @@
Give priority to using the current class constructor in
:func:`inspect.signature`. Patch by Weipeng Hong.