Skip to content

Commit

Permalink
pythongh-87106: Fix inspect.signature.bind() handling of positional-o…
Browse files Browse the repository at this point in the history
…nly arguments with **kwargs (pythonGH-103404)

(cherry picked from commit 9c15202)

Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
  • Loading branch information
jacobtylerwalls authored and miss-islington committed May 13, 2024
1 parent 76dc1bf commit 4c23ae3
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 17 deletions.
28 changes: 16 additions & 12 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3129,6 +3129,8 @@ def _bind(self, args, kwargs, *, partial=False):
parameters_ex = ()
arg_vals = iter(args)

pos_only_param_in_kwargs = []

while True:
# Let's iterate through the positional arguments and corresponding
# parameters
Expand All @@ -3149,10 +3151,10 @@ def _bind(self, args, kwargs, *, partial=False):
break
elif param.name in kwargs:
if param.kind == _POSITIONAL_ONLY:
msg = '{arg!r} parameter is positional only, ' \
'but was passed as a keyword'
msg = msg.format(arg=param.name)
raise TypeError(msg) from None
# Raise a TypeError once we are sure there is no
# **kwargs param later.
pos_only_param_in_kwargs.append(param)
continue
parameters_ex = (param,)
break
elif (param.kind == _VAR_KEYWORD or
Expand Down Expand Up @@ -3234,20 +3236,22 @@ def _bind(self, args, kwargs, *, partial=False):
format(arg=param_name)) from None

else:
if param.kind == _POSITIONAL_ONLY:
# This should never happen in case of a properly built
# Signature object (but let's have this check here
# to ensure correct behaviour just in case)
raise TypeError('{arg!r} parameter is positional only, '
'but was passed as a keyword'. \
format(arg=param.name))

arguments[param_name] = arg_val

if kwargs:
if kwargs_param is not None:
# Process our '**kwargs'-like parameter
arguments[kwargs_param.name] = kwargs
elif pos_only_param_in_kwargs:
raise TypeError(
'got some positional-only arguments passed as '
'keyword arguments: {arg!r}'.format(
arg=', '.join(
param.name
for param in pos_only_param_in_kwargs
),
),
)
else:
raise TypeError(
'got an unexpected keyword argument {arg!r}'.format(
Expand Down
25 changes: 20 additions & 5 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -4691,15 +4691,30 @@ def test(a_po, b_po, c_po=3, /, foo=42, *, bar=50, **kwargs):
self.assertEqual(self.call(test, 1, 2, foo=4, bar=5),
(1, 2, 3, 4, 5, {}))

with self.assertRaisesRegex(TypeError, "but was passed as a keyword"):
self.call(test, 1, 2, foo=4, bar=5, c_po=10)
self.assertEqual(self.call(test, 1, 2, foo=4, bar=5, c_po=10),
(1, 2, 3, 4, 5, {'c_po': 10}))

with self.assertRaisesRegex(TypeError, "parameter is positional only"):
self.call(test, 1, 2, c_po=4)
self.assertEqual(self.call(test, 1, 2, 30, c_po=31, foo=4, bar=5),
(1, 2, 30, 4, 5, {'c_po': 31}))

with self.assertRaisesRegex(TypeError, "parameter is positional only"):
self.assertEqual(self.call(test, 1, 2, 30, foo=4, bar=5, c_po=31),
(1, 2, 30, 4, 5, {'c_po': 31}))

self.assertEqual(self.call(test, 1, 2, c_po=4),
(1, 2, 3, 42, 50, {'c_po': 4}))

with self.assertRaisesRegex(TypeError, "missing 2 required positional arguments"):
self.call(test, a_po=1, b_po=2)

def without_var_kwargs(c_po=3, d_po=4, /):
return c_po, d_po

with self.assertRaisesRegex(
TypeError,
"positional-only arguments passed as keyword arguments: 'c_po, d_po'",
):
self.call(without_var_kwargs, c_po=33, d_po=44)

def test_signature_bind_with_self_arg(self):
# Issue #17071: one of the parameters is named "self
def test(a, self, b):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fixed handling in :meth:`inspect.signature.bind` of keyword arguments having
the same name as positional-only arguments when a variadic keyword argument
(e.g. ``**kwargs``) is present.

0 comments on commit 4c23ae3

Please sign in to comment.