summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2024-05-13 08:35:31 (GMT)
committerGitHub <noreply@github.com>2024-05-13 08:35:31 (GMT)
commita8ea9669aa79b0d29e26a30824b7516efa612a02 (patch)
tree6098aa2846d6b35f7759d658395dfbaa67ad1d84 /Lib
parent9d2c10bee3d8c756d395b840192dc3efd6ba8134 (diff)
downloadcpython-a8ea9669aa79b0d29e26a30824b7516efa612a02.zip
cpython-a8ea9669aa79b0d29e26a30824b7516efa612a02.tar.gz
cpython-a8ea9669aa79b0d29e26a30824b7516efa612a02.tar.bz2
[3.13] gh-87106: Fix inspect.signature.bind() handling of positional-only arguments with **kwargs (GH-103404) (#118985)
(cherry picked from commit 9c1520244151f36e010c1b04bedf14747a28517d) Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
Diffstat (limited to 'Lib')
-rw-r--r--Lib/inspect.py28
-rw-r--r--Lib/test/test_inspect/test_inspect.py25
2 files changed, 36 insertions, 17 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 84260b2..e6e49a4 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -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(
diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py
index 8bd1303..011d42f 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -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):