From 578f998916cfbc97f14829b7f841886f67b198b4 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sat, 15 Apr 2023 12:46:17 +0300 Subject: [PATCH 1/3] gh-103556: [inspect.Signature] disallow pos-or-kw params without default after pos-only with default --- Lib/inspect.py | 7 +++- Lib/test/test_inspect.py | 40 ++++++++++++++++--- ...-04-15-12-19-14.gh-issue-103556.TEf-2m.rst | 3 ++ 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-15-12-19-14.gh-issue-103556.TEf-2m.rst diff --git a/Lib/inspect.py b/Lib/inspect.py index 4242b40c2a08df..f2240fa84c7468 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -3021,7 +3021,12 @@ def __init__(self, parameters=None, *, return_annotation=_empty, kind.description) raise ValueError(msg) elif kind > top_kind: - kind_defaults = False + if (top_kind != _POSITIONAL_ONLY + and kind != _POSITIONAL_OR_KEYWORD): + # We still have to maintain defaults in cases like + # def some(pod=42, /, pk=1): ... + # Here `pk` must have a default value. + kind_defaults = False top_kind = kind if kind in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD): diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index cfdb992654c252..03a36aba90fedd 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -2462,18 +2462,43 @@ def test_signature_object(self): self.assertEqual(str(S()), '()') self.assertEqual(repr(S().parameters), 'mappingproxy(OrderedDict())') - def test(po, pk, pod=42, pkd=100, *args, ko, **kwargs): + def test(po, /, pk, pkd=100, *args, ko, kod=10, **kwargs): pass + sig = inspect.signature(test) - po = sig.parameters['po'].replace(kind=P.POSITIONAL_ONLY) - pod = sig.parameters['pod'].replace(kind=P.POSITIONAL_ONLY) + self.assertTrue(repr(sig).startswith(' {42:'ham'}: pass foo_partial = functools.partial(foo, a=1) diff --git a/Misc/NEWS.d/next/Library/2023-04-15-12-19-14.gh-issue-103556.TEf-2m.rst b/Misc/NEWS.d/next/Library/2023-04-15-12-19-14.gh-issue-103556.TEf-2m.rst new file mode 100644 index 00000000000000..fe2267b7b79019 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-15-12-19-14.gh-issue-103556.TEf-2m.rst @@ -0,0 +1,3 @@ +Now creating :class:`inspect.Signature` objects with positional-only +parameter with a default followed by a positional-or-keyword parameter +without one is impossible. From a4cf53818329a5644fc0265b324b77a01f7a6a52 Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Sat, 15 Apr 2023 14:42:47 +0300 Subject: [PATCH 2/3] Update Lib/test/test_inspect.py --- Lib/test/test_inspect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 03a36aba90fedd..eca3479e0cd06c 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -2469,8 +2469,8 @@ def test(po, /, pk, pkd=100, *args, ko, kod=10, **kwargs): self.assertTrue(repr(sig).startswith(' Date: Wed, 19 Apr 2023 11:39:21 +0300 Subject: [PATCH 3/3] Address review --- Lib/inspect.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Lib/inspect.py b/Lib/inspect.py index f2240fa84c7468..6d1d7b766cb3bb 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -3006,7 +3006,7 @@ def __init__(self, parameters=None, *, return_annotation=_empty, if __validate_parameters__: params = OrderedDict() top_kind = _POSITIONAL_ONLY - kind_defaults = False + seen_default = False for param in parameters: kind = param.kind @@ -3021,26 +3021,19 @@ def __init__(self, parameters=None, *, return_annotation=_empty, kind.description) raise ValueError(msg) elif kind > top_kind: - if (top_kind != _POSITIONAL_ONLY - and kind != _POSITIONAL_OR_KEYWORD): - # We still have to maintain defaults in cases like - # def some(pod=42, /, pk=1): ... - # Here `pk` must have a default value. - kind_defaults = False top_kind = kind if kind in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD): if param.default is _empty: - if kind_defaults: + if seen_default: # No default for this parameter, but the - # previous parameter of the same kind had - # a default + # previous parameter of had a default msg = 'non-default argument follows default ' \ 'argument' raise ValueError(msg) else: # There is a default for this parameter. - kind_defaults = True + seen_default = True if name in params: msg = 'duplicate parameter name: {!r}'.format(name)