Skip to content

Commit 6aab5f9

Browse files
hongweipengambv
andauthored
bpo-40897:Give priority to using the current class constructor in inspect.signature (python#27177)
Co-authored-by: Łukasz Langa <[email protected]>
1 parent 8ce3008 commit 6aab5f9

File tree

3 files changed

+59
-8
lines changed

3 files changed

+59
-8
lines changed

Lib/inspect.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2444,15 +2444,23 @@ def _signature_from_callable(obj, *,
24442444
if call is not None:
24452445
sig = _get_signature_of(call)
24462446
else:
2447-
# Now we check if the 'obj' class has a '__new__' method
2447+
factory_method = None
24482448
new = _signature_get_user_defined_method(obj, '__new__')
2449-
if new is not None:
2450-
sig = _get_signature_of(new)
2451-
else:
2452-
# Finally, we should have at least __init__ implemented
2453-
init = _signature_get_user_defined_method(obj, '__init__')
2454-
if init is not None:
2455-
sig = _get_signature_of(init)
2449+
init = _signature_get_user_defined_method(obj, '__init__')
2450+
# Now we check if the 'obj' class has an own '__new__' method
2451+
if '__new__' in obj.__dict__:
2452+
factory_method = new
2453+
# or an own '__init__' method
2454+
elif '__init__' in obj.__dict__:
2455+
factory_method = init
2456+
# If not, we take inherited '__new__' or '__init__', if present
2457+
elif new is not None:
2458+
factory_method = new
2459+
elif init is not None:
2460+
factory_method = init
2461+
2462+
if factory_method is not None:
2463+
sig = _get_signature_of(factory_method)
24562464

24572465
if sig is None:
24582466
# At this point we know, that `obj` is a class, with no user-

Lib/test/test_inspect.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3063,6 +3063,47 @@ def __init__(self, b):
30633063
('bar', 2, ..., "keyword_only")),
30643064
...))
30653065

3066+
def test_signature_on_subclass(self):
3067+
class A:
3068+
def __new__(cls, a=1, *args, **kwargs):
3069+
return object.__new__(cls)
3070+
class B(A):
3071+
def __init__(self, b):
3072+
pass
3073+
class C(A):
3074+
def __new__(cls, a=1, b=2, *args, **kwargs):
3075+
return object.__new__(cls)
3076+
class D(A):
3077+
pass
3078+
3079+
self.assertEqual(self.signature(B),
3080+
((('b', ..., ..., "positional_or_keyword"),),
3081+
...))
3082+
self.assertEqual(self.signature(C),
3083+
((('a', 1, ..., 'positional_or_keyword'),
3084+
('b', 2, ..., 'positional_or_keyword'),
3085+
('args', ..., ..., 'var_positional'),
3086+
('kwargs', ..., ..., 'var_keyword')),
3087+
...))
3088+
self.assertEqual(self.signature(D),
3089+
((('a', 1, ..., 'positional_or_keyword'),
3090+
('args', ..., ..., 'var_positional'),
3091+
('kwargs', ..., ..., 'var_keyword')),
3092+
...))
3093+
3094+
def test_signature_on_generic_subclass(self):
3095+
from typing import Generic, TypeVar
3096+
3097+
T = TypeVar('T')
3098+
3099+
class A(Generic[T]):
3100+
def __init__(self, *, a: int) -> None:
3101+
pass
3102+
3103+
self.assertEqual(self.signature(A),
3104+
((('a', ..., int, 'keyword_only'),),
3105+
None))
3106+
30663107
@unittest.skipIf(MISSING_C_DOCSTRINGS,
30673108
"Signature information for builtins requires docstrings")
30683109
def test_signature_on_class_without_init(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Give priority to using the current class constructor in
2+
:func:`inspect.signature`. Patch by Weipeng Hong.

0 commit comments

Comments
 (0)