Skip to content

Commit 6b58d41

Browse files
authored
gh-103556: [inspect.Signature] disallow pos-or-kw params without default after pos-only with default (#103557)
1 parent 7d20783 commit 6b58d41

File tree

3 files changed

+41
-12
lines changed

3 files changed

+41
-12
lines changed

Lib/inspect.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -3006,7 +3006,7 @@ def __init__(self, parameters=None, *, return_annotation=_empty,
30063006
if __validate_parameters__:
30073007
params = OrderedDict()
30083008
top_kind = _POSITIONAL_ONLY
3009-
kind_defaults = False
3009+
seen_default = False
30103010

30113011
for param in parameters:
30123012
kind = param.kind
@@ -3021,21 +3021,19 @@ def __init__(self, parameters=None, *, return_annotation=_empty,
30213021
kind.description)
30223022
raise ValueError(msg)
30233023
elif kind > top_kind:
3024-
kind_defaults = False
30253024
top_kind = kind
30263025

30273026
if kind in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD):
30283027
if param.default is _empty:
3029-
if kind_defaults:
3028+
if seen_default:
30303029
# No default for this parameter, but the
3031-
# previous parameter of the same kind had
3032-
# a default
3030+
# previous parameter of had a default
30333031
msg = 'non-default argument follows default ' \
30343032
'argument'
30353033
raise ValueError(msg)
30363034
else:
30373035
# There is a default for this parameter.
3038-
kind_defaults = True
3036+
seen_default = True
30393037

30403038
if name in params:
30413039
msg = 'duplicate parameter name: {!r}'.format(name)

Lib/test/test_inspect.py

+34-6
Original file line numberDiff line numberDiff line change
@@ -2463,18 +2463,43 @@ def test_signature_object(self):
24632463
self.assertEqual(str(S()), '()')
24642464
self.assertEqual(repr(S().parameters), 'mappingproxy(OrderedDict())')
24652465

2466-
def test(po, pk, pod=42, pkd=100, *args, ko, **kwargs):
2466+
def test(po, /, pk, pkd=100, *args, ko, kod=10, **kwargs):
24672467
pass
2468+
24682469
sig = inspect.signature(test)
2469-
po = sig.parameters['po'].replace(kind=P.POSITIONAL_ONLY)
2470-
pod = sig.parameters['pod'].replace(kind=P.POSITIONAL_ONLY)
2470+
self.assertTrue(repr(sig).startswith('<Signature'))
2471+
self.assertTrue('(po, /, pk' in repr(sig))
2472+
2473+
# We need two functions, because it is impossible to represent
2474+
# all param kinds in a single one.
2475+
def test2(pod=42, /):
2476+
pass
2477+
2478+
sig2 = inspect.signature(test2)
2479+
self.assertTrue(repr(sig2).startswith('<Signature'))
2480+
self.assertTrue('(pod=42, /)' in repr(sig2))
2481+
2482+
po = sig.parameters['po']
2483+
pod = sig2.parameters['pod']
24712484
pk = sig.parameters['pk']
24722485
pkd = sig.parameters['pkd']
24732486
args = sig.parameters['args']
24742487
ko = sig.parameters['ko']
2488+
kod = sig.parameters['kod']
24752489
kwargs = sig.parameters['kwargs']
24762490

24772491
S((po, pk, args, ko, kwargs))
2492+
S((po, pk, ko, kod))
2493+
S((po, pod, ko))
2494+
S((po, pod, kod))
2495+
S((pod, ko, kod))
2496+
S((pod, kod))
2497+
S((pod, args, kod, kwargs))
2498+
# keyword-only parameters without default values
2499+
# can follow keyword-only parameters with default values:
2500+
S((kod, ko))
2501+
S((kod, ko, kwargs))
2502+
S((args, kod, ko))
24782503

24792504
with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
24802505
S((pk, po, args, ko, kwargs))
@@ -2495,15 +2520,18 @@ def test(po, pk, pod=42, pkd=100, *args, ko, **kwargs):
24952520
with self.assertRaisesRegex(ValueError, 'follows default argument'):
24962521
S((pod, po))
24972522

2523+
with self.assertRaisesRegex(ValueError, 'follows default argument'):
2524+
S((pod, pk))
2525+
2526+
with self.assertRaisesRegex(ValueError, 'follows default argument'):
2527+
S((po, pod, pk))
2528+
24982529
with self.assertRaisesRegex(ValueError, 'follows default argument'):
24992530
S((po, pkd, pk))
25002531

25012532
with self.assertRaisesRegex(ValueError, 'follows default argument'):
25022533
S((pkd, pk))
25032534

2504-
self.assertTrue(repr(sig).startswith('<Signature'))
2505-
self.assertTrue('(po, pk' in repr(sig))
2506-
25072535
def test_signature_object_pickle(self):
25082536
def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass
25092537
foo_partial = functools.partial(foo, a=1)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Now creating :class:`inspect.Signature` objects with positional-only
2+
parameter with a default followed by a positional-or-keyword parameter
3+
without one is impossible.

0 commit comments

Comments
 (0)