diff --git a/Lib/inspect.py b/Lib/inspect.py index 8bb3a375735af6..e076e082956695 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2835,16 +2835,24 @@ class BoundArguments: Dict of keyword arguments values. """ - __slots__ = ('arguments', '_signature', '__weakref__') + __slots__ = ('arguments', '_signature', '_kwarg_names', '__weakref__') - def __init__(self, signature, arguments): + def __init__(self, signature, arguments, kwarg_names=None): self.arguments = arguments self._signature = signature + if kwarg_names is None: + self._kwarg_names = {} + else: + self._kwarg_names = kwarg_names @property def signature(self): return self._signature + @property + def kwarg_names(self): + return self.kwarg_names + @property def args(self): args = [] @@ -2852,6 +2860,10 @@ def args(self): if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): break + if (param.kind == _POSITIONAL_OR_KEYWORD and \ + param_name in self._kwarg_names): + break + try: arg = self.arguments[param_name] except KeyError: @@ -2876,6 +2888,9 @@ def kwargs(self): if not kwargs_started: if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): kwargs_started = True + elif (param.kind == _POSITIONAL_OR_KEYWORD and + param_name in self._kwarg_names): + kwargs_started = True else: if param_name not in self.arguments: kwargs_started = True @@ -2932,7 +2947,8 @@ def __eq__(self, other): if not isinstance(other, BoundArguments): return NotImplemented return (self.signature == other.signature and - self.arguments == other.arguments) + self.arguments == other.arguments and + self.kwarg_names == other.kwarg_names) def __setstate__(self, state): self._signature = state['_signature'] @@ -3088,6 +3104,7 @@ def _bind(self, args, kwargs, *, partial=False): """Private method. Don't use directly.""" arguments = {} + kwarg_names = set(kwargs.keys()) parameters = iter(self.parameters.values()) parameters_ex = () @@ -3217,7 +3234,7 @@ def _bind(self, args, kwargs, *, partial=False): 'got an unexpected keyword argument {arg!r}'.format( arg=next(iter(kwargs)))) - return self._bound_arguments_cls(self, arguments) + return self._bound_arguments_cls(self, arguments, kwarg_names) def bind(self, /, *args, **kwargs): """Get a BoundArguments object, that maps the passed `args` diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 92aba519d28a08..2fe7ca9d1aa4d2 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -4084,7 +4084,7 @@ def test(a, self, b): ba = sig.bind(1, 2, 3) self.assertEqual(ba.args, (1, 2, 3)) ba = sig.bind(1, self=2, b=3) - self.assertEqual(ba.args, (1, 2, 3)) + self.assertEqual(ba.args, (1,)) def test_signature_bind_vararg_name(self): def test(a, *args): diff --git a/Misc/NEWS.d/next/Library/2023-03-18-21-53-08.gh-issue-102551.HjjSLL.rst b/Misc/NEWS.d/next/Library/2023-03-18-21-53-08.gh-issue-102551.HjjSLL.rst new file mode 100644 index 00000000000000..b7a7847665cc3c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-03-18-21-53-08.gh-issue-102551.HjjSLL.rst @@ -0,0 +1 @@ +Fixed inspect.signature.bind behavior on keyword arguments