mirror of https://github.com/python/cpython
[3.13] gh-87106: Fix inspect.signature.bind() handling of positional-only arguments with **kwargs (GH-103404) (#118985)
(cherry picked from commit 9c15202441
)
Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
This commit is contained in:
parent
9d2c10bee3
commit
a8ea9669aa
|
@ -3106,6 +3106,8 @@ class Signature:
|
|||
parameters_ex = ()
|
||||
arg_vals = iter(args)
|
||||
|
||||
pos_only_param_in_kwargs = []
|
||||
|
||||
while True:
|
||||
# Let's iterate through the positional arguments and corresponding
|
||||
# parameters
|
||||
|
@ -3126,10 +3128,10 @@ class Signature:
|
|||
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
|
||||
|
@ -3211,20 +3213,22 @@ class Signature:
|
|||
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(
|
||||
|
|
|
@ -5089,15 +5089,30 @@ class TestSignatureBind(unittest.TestCase):
|
|||
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):
|
||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue