Skip to content

Commit 9c15202

Browse files
gh-87106: Fix inspect.signature.bind() handling of positional-only arguments with **kwargs (GH-103404)
1 parent a705c1e commit 9c15202

File tree

3 files changed

+39
-17
lines changed

3 files changed

+39
-17
lines changed

Diff for: Lib/inspect.py

+16-12
Original file line numberDiff line numberDiff line change
@@ -3106,6 +3106,8 @@ def _bind(self, args, kwargs, *, partial=False):
31063106
parameters_ex = ()
31073107
arg_vals = iter(args)
31083108

3109+
pos_only_param_in_kwargs = []
3110+
31093111
while True:
31103112
# Let's iterate through the positional arguments and corresponding
31113113
# parameters
@@ -3126,10 +3128,10 @@ def _bind(self, args, kwargs, *, partial=False):
31263128
break
31273129
elif param.name in kwargs:
31283130
if param.kind == _POSITIONAL_ONLY:
3129-
msg = '{arg!r} parameter is positional only, ' \
3130-
'but was passed as a keyword'
3131-
msg = msg.format(arg=param.name)
3132-
raise TypeError(msg) from None
3131+
# Raise a TypeError once we are sure there is no
3132+
# **kwargs param later.
3133+
pos_only_param_in_kwargs.append(param)
3134+
continue
31333135
parameters_ex = (param,)
31343136
break
31353137
elif (param.kind == _VAR_KEYWORD or
@@ -3211,20 +3213,22 @@ def _bind(self, args, kwargs, *, partial=False):
32113213
format(arg=param_name)) from None
32123214

32133215
else:
3214-
if param.kind == _POSITIONAL_ONLY:
3215-
# This should never happen in case of a properly built
3216-
# Signature object (but let's have this check here
3217-
# to ensure correct behaviour just in case)
3218-
raise TypeError('{arg!r} parameter is positional only, '
3219-
'but was passed as a keyword'. \
3220-
format(arg=param.name))
3221-
32223216
arguments[param_name] = arg_val
32233217

32243218
if kwargs:
32253219
if kwargs_param is not None:
32263220
# Process our '**kwargs'-like parameter
32273221
arguments[kwargs_param.name] = kwargs
3222+
elif pos_only_param_in_kwargs:
3223+
raise TypeError(
3224+
'got some positional-only arguments passed as '
3225+
'keyword arguments: {arg!r}'.format(
3226+
arg=', '.join(
3227+
param.name
3228+
for param in pos_only_param_in_kwargs
3229+
),
3230+
),
3231+
)
32283232
else:
32293233
raise TypeError(
32303234
'got an unexpected keyword argument {arg!r}'.format(

Diff for: Lib/test/test_inspect/test_inspect.py

+20-5
Original file line numberDiff line numberDiff line change
@@ -5089,15 +5089,30 @@ def test(a_po, b_po, c_po=3, /, foo=42, *, bar=50, **kwargs):
50895089
self.assertEqual(self.call(test, 1, 2, foo=4, bar=5),
50905090
(1, 2, 3, 4, 5, {}))
50915091

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

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

5098-
with self.assertRaisesRegex(TypeError, "parameter is positional only"):
5098+
self.assertEqual(self.call(test, 1, 2, 30, foo=4, bar=5, c_po=31),
5099+
(1, 2, 30, 4, 5, {'c_po': 31}))
5100+
5101+
self.assertEqual(self.call(test, 1, 2, c_po=4),
5102+
(1, 2, 3, 42, 50, {'c_po': 4}))
5103+
5104+
with self.assertRaisesRegex(TypeError, "missing 2 required positional arguments"):
50995105
self.call(test, a_po=1, b_po=2)
51005106

5107+
def without_var_kwargs(c_po=3, d_po=4, /):
5108+
return c_po, d_po
5109+
5110+
with self.assertRaisesRegex(
5111+
TypeError,
5112+
"positional-only arguments passed as keyword arguments: 'c_po, d_po'",
5113+
):
5114+
self.call(without_var_kwargs, c_po=33, d_po=44)
5115+
51015116
def test_signature_bind_with_self_arg(self):
51025117
# Issue #17071: one of the parameters is named "self
51035118
def test(a, self, b):
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)