Skip to content

Commit b2fdae9

Browse files
[3.11] gh-103556: [inspect.Signature] disallow pos-or-kw params without default after pos-only with default (GH-103557) (#103675)
1 parent 2b5dbd1 commit b2fdae9

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
@@ -2978,7 +2978,7 @@ def __init__(self, parameters=None, *, return_annotation=_empty,
29782978
if __validate_parameters__:
29792979
params = OrderedDict()
29802980
top_kind = _POSITIONAL_ONLY
2981-
kind_defaults = False
2981+
seen_default = False
29822982

29832983
for param in parameters:
29842984
kind = param.kind
@@ -2993,21 +2993,19 @@ def __init__(self, parameters=None, *, return_annotation=_empty,
29932993
kind.description)
29942994
raise ValueError(msg)
29952995
elif kind > top_kind:
2996-
kind_defaults = False
29972996
top_kind = kind
29982997

29992998
if kind in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD):
30002999
if param.default is _empty:
3001-
if kind_defaults:
3000+
if seen_default:
30023001
# No default for this parameter, but the
3003-
# previous parameter of the same kind had
3004-
# a default
3002+
# previous parameter of had a default
30053003
msg = 'non-default argument follows default ' \
30063004
'argument'
30073005
raise ValueError(msg)
30083006
else:
30093007
# There is a default for this parameter.
3010-
kind_defaults = True
3008+
seen_default = True
30113009

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

Lib/test/test_inspect.py

+34-6
Original file line numberDiff line numberDiff line change
@@ -2301,18 +2301,43 @@ def test_signature_object(self):
23012301
self.assertEqual(str(S()), '()')
23022302
self.assertEqual(repr(S().parameters), 'mappingproxy(OrderedDict())')
23032303

2304-
def test(po, pk, pod=42, pkd=100, *args, ko, **kwargs):
2304+
def test(po, /, pk, pkd=100, *args, ko, kod=10, **kwargs):
23052305
pass
2306+
23062307
sig = inspect.signature(test)
2307-
po = sig.parameters['po'].replace(kind=P.POSITIONAL_ONLY)
2308-
pod = sig.parameters['pod'].replace(kind=P.POSITIONAL_ONLY)
2308+
self.assertTrue(repr(sig).startswith('<Signature'))
2309+
self.assertTrue('(po, /, pk' in repr(sig))
2310+
2311+
# We need two functions, because it is impossible to represent
2312+
# all param kinds in a single one.
2313+
def test2(pod=42, /):
2314+
pass
2315+
2316+
sig2 = inspect.signature(test2)
2317+
self.assertTrue(repr(sig2).startswith('<Signature'))
2318+
self.assertTrue('(pod=42, /)' in repr(sig2))
2319+
2320+
po = sig.parameters['po']
2321+
pod = sig2.parameters['pod']
23092322
pk = sig.parameters['pk']
23102323
pkd = sig.parameters['pkd']
23112324
args = sig.parameters['args']
23122325
ko = sig.parameters['ko']
2326+
kod = sig.parameters['kod']
23132327
kwargs = sig.parameters['kwargs']
23142328

23152329
S((po, pk, args, ko, kwargs))
2330+
S((po, pk, ko, kod))
2331+
S((po, pod, ko))
2332+
S((po, pod, kod))
2333+
S((pod, ko, kod))
2334+
S((pod, kod))
2335+
S((pod, args, kod, kwargs))
2336+
# keyword-only parameters without default values
2337+
# can follow keyword-only parameters with default values:
2338+
S((kod, ko))
2339+
S((kod, ko, kwargs))
2340+
S((args, kod, ko))
23162341

23172342
with self.assertRaisesRegex(ValueError, 'wrong parameter order'):
23182343
S((pk, po, args, ko, kwargs))
@@ -2333,15 +2358,18 @@ def test(po, pk, pod=42, pkd=100, *args, ko, **kwargs):
23332358
with self.assertRaisesRegex(ValueError, 'follows default argument'):
23342359
S((pod, po))
23352360

2361+
with self.assertRaisesRegex(ValueError, 'follows default argument'):
2362+
S((pod, pk))
2363+
2364+
with self.assertRaisesRegex(ValueError, 'follows default argument'):
2365+
S((po, pod, pk))
2366+
23362367
with self.assertRaisesRegex(ValueError, 'follows default argument'):
23372368
S((po, pkd, pk))
23382369

23392370
with self.assertRaisesRegex(ValueError, 'follows default argument'):
23402371
S((pkd, pk))
23412372

2342-
self.assertTrue(repr(sig).startswith('<Signature'))
2343-
self.assertTrue('(po, pk' in repr(sig))
2344-
23452373
def test_signature_object_pickle(self):
23462374
def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass
23472375
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)