Skip to content

Commit 35649df

Browse files
[3.12] gh-87106: Fix inspect.signature.bind() handling of positional-only arguments with **kwargs (GH-103404) (GH-118984)
(cherry picked from commit 9c15202) Co-authored-by: Jacob Walls <[email protected]>
1 parent 76dc1bf commit 35649df

File tree

3 files changed

+39
-17
lines changed

3 files changed

+39
-17
lines changed

Lib/inspect.py

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3129,6 +3129,8 @@ def _bind(self, args, kwargs, *, partial=False):
31293129
parameters_ex = ()
31303130
arg_vals = iter(args)
31313131

3132+
pos_only_param_in_kwargs = []
3133+
31323134
while True:
31333135
# Let's iterate through the positional arguments and corresponding
31343136
# parameters
@@ -3149,10 +3151,10 @@ def _bind(self, args, kwargs, *, partial=False):
31493151
break
31503152
elif param.name in kwargs:
31513153
if param.kind == _POSITIONAL_ONLY:
3152-
msg = '{arg!r} parameter is positional only, ' \
3153-
'but was passed as a keyword'
3154-
msg = msg.format(arg=param.name)
3155-
raise TypeError(msg) from None
3154+
# Raise a TypeError once we are sure there is no
3155+
# **kwargs param later.
3156+
pos_only_param_in_kwargs.append(param)
3157+
continue
31563158
parameters_ex = (param,)
31573159
break
31583160
elif (param.kind == _VAR_KEYWORD or
@@ -3234,20 +3236,22 @@ def _bind(self, args, kwargs, *, partial=False):
32343236
format(arg=param_name)) from None
32353237

32363238
else:
3237-
if param.kind == _POSITIONAL_ONLY:
3238-
# This should never happen in case of a properly built
3239-
# Signature object (but let's have this check here
3240-
# to ensure correct behaviour just in case)
3241-
raise TypeError('{arg!r} parameter is positional only, '
3242-
'but was passed as a keyword'. \
3243-
format(arg=param.name))
3244-
32453239
arguments[param_name] = arg_val
32463240

32473241
if kwargs:
32483242
if kwargs_param is not None:
32493243
# Process our '**kwargs'-like parameter
32503244
arguments[kwargs_param.name] = kwargs
3245+
elif pos_only_param_in_kwargs:
3246+
raise TypeError(
3247+
'got some positional-only arguments passed as '
3248+
'keyword arguments: {arg!r}'.format(
3249+
arg=', '.join(
3250+
param.name
3251+
for param in pos_only_param_in_kwargs
3252+
),
3253+
),
3254+
)
32513255
else:
32523256
raise TypeError(
32533257
'got an unexpected keyword argument {arg!r}'.format(

Lib/test/test_inspect/test_inspect.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4691,15 +4691,30 @@ def test(a_po, b_po, c_po=3, /, foo=42, *, bar=50, **kwargs):
46914691
self.assertEqual(self.call(test, 1, 2, foo=4, bar=5),
46924692
(1, 2, 3, 4, 5, {}))
46934693

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

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

4700-
with self.assertRaisesRegex(TypeError, "parameter is positional only"):
4700+
self.assertEqual(self.call(test, 1, 2, 30, foo=4, bar=5, c_po=31),
4701+
(1, 2, 30, 4, 5, {'c_po': 31}))
4702+
4703+
self.assertEqual(self.call(test, 1, 2, c_po=4),
4704+
(1, 2, 3, 42, 50, {'c_po': 4}))
4705+
4706+
with self.assertRaisesRegex(TypeError, "missing 2 required positional arguments"):
47014707
self.call(test, a_po=1, b_po=2)
47024708

4709+
def without_var_kwargs(c_po=3, d_po=4, /):
4710+
return c_po, d_po
4711+
4712+
with self.assertRaisesRegex(
4713+
TypeError,
4714+
"positional-only arguments passed as keyword arguments: 'c_po, d_po'",
4715+
):
4716+
self.call(without_var_kwargs, c_po=33, d_po=44)
4717+
47034718
def test_signature_bind_with_self_arg(self):
47044719
# Issue #17071: one of the parameters is named "self
47054720
def test(a, self, b):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed handling in :meth:`inspect.signature.bind` of keyword arguments having
2+
the same name as positional-only arguments when a variadic keyword argument
3+
(e.g. ``**kwargs``) is present.

0 commit comments

Comments
 (0)