bpo-37645: add new function _PyObject_FunctionStr() (GH-14890)

Additional note: the `method_check_args` function in `Objects/descrobject.c` is written in such a way that it applies to all kinds of descriptors. In particular, a future re-implementation of `wrapper_descriptor` could use that code.

CC @vstinner @encukou 


https://bugs.python.org/issue37645



Automerge-Triggered-By: @encukou
This commit is contained in:
Jeroen Demeyer 2019-11-05 16:48:04 +01:00 committed by Miss Islington (bot)
parent b3966639d2
commit bf17d41826
11 changed files with 171 additions and 94 deletions

View File

@ -196,6 +196,7 @@ Object Protocol
This function now includes a debug assertion to help ensure that it This function now includes a debug assertion to help ensure that it
does not silently discard an active exception. does not silently discard an active exception.
.. c:function:: PyObject* PyObject_Bytes(PyObject *o) .. c:function:: PyObject* PyObject_Bytes(PyObject *o)
.. index:: builtin: bytes .. index:: builtin: bytes

View File

@ -348,6 +348,7 @@ static inline void _Py_Dealloc_inline(PyObject *op)
} }
#define _Py_Dealloc(op) _Py_Dealloc_inline(op) #define _Py_Dealloc(op) _Py_Dealloc_inline(op)
PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
/* Safely decref `op` and set `op` to `op2`. /* Safely decref `op` and set `op` to `op2`.
* *

View File

@ -74,7 +74,7 @@ class CFunctionCallsErrorMessages(unittest.TestCase):
self.assertRaisesRegex(TypeError, msg, bool, x=2) self.assertRaisesRegex(TypeError, msg, bool, x=2)
def test_varargs4_kw(self): def test_varargs4_kw(self):
msg = r"^index\(\) takes no keyword arguments$" msg = r"^list[.]index\(\) takes no keyword arguments$"
self.assertRaisesRegex(TypeError, msg, [].index, x=2) self.assertRaisesRegex(TypeError, msg, [].index, x=2)
def test_varargs5_kw(self): def test_varargs5_kw(self):
@ -90,19 +90,19 @@ class CFunctionCallsErrorMessages(unittest.TestCase):
self.assertRaisesRegex(TypeError, msg, next, x=2) self.assertRaisesRegex(TypeError, msg, next, x=2)
def test_varargs8_kw(self): def test_varargs8_kw(self):
msg = r"^pack\(\) takes no keyword arguments$" msg = r"^_struct[.]pack\(\) takes no keyword arguments$"
self.assertRaisesRegex(TypeError, msg, struct.pack, x=2) self.assertRaisesRegex(TypeError, msg, struct.pack, x=2)
def test_varargs9_kw(self): def test_varargs9_kw(self):
msg = r"^pack_into\(\) takes no keyword arguments$" msg = r"^_struct[.]pack_into\(\) takes no keyword arguments$"
self.assertRaisesRegex(TypeError, msg, struct.pack_into, x=2) self.assertRaisesRegex(TypeError, msg, struct.pack_into, x=2)
def test_varargs10_kw(self): def test_varargs10_kw(self):
msg = r"^index\(\) takes no keyword arguments$" msg = r"^deque[.]index\(\) takes no keyword arguments$"
self.assertRaisesRegex(TypeError, msg, collections.deque().index, x=2) self.assertRaisesRegex(TypeError, msg, collections.deque().index, x=2)
def test_varargs11_kw(self): def test_varargs11_kw(self):
msg = r"^pack\(\) takes no keyword arguments$" msg = r"^Struct[.]pack\(\) takes no keyword arguments$"
self.assertRaisesRegex(TypeError, msg, struct.Struct.pack, struct.Struct(""), x=2) self.assertRaisesRegex(TypeError, msg, struct.Struct.pack, struct.Struct(""), x=2)
def test_varargs12_kw(self): def test_varargs12_kw(self):

View File

@ -1967,7 +1967,7 @@ order (MRO) for bases """
# different error messages. # different error messages.
set_add = set.add set_add = set.add
expected_errmsg = "descriptor 'add' of 'set' object needs an argument" expected_errmsg = "unbound method set.add() needs an argument"
with self.assertRaises(TypeError) as cm: with self.assertRaises(TypeError) as cm:
set_add() set_add()

View File

@ -52,15 +52,15 @@ Here we add keyword arguments
>>> f(1, 2, **{'a': -1, 'b': 5}, **{'a': 4, 'c': 6}) >>> f(1, 2, **{'a': -1, 'b': 5}, **{'a': 4, 'c': 6})
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() got multiple values for keyword argument 'a' TypeError: test.test_extcall.f() got multiple values for keyword argument 'a'
>>> f(1, 2, **{'a': -1, 'b': 5}, a=4, c=6) >>> f(1, 2, **{'a': -1, 'b': 5}, a=4, c=6)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() got multiple values for keyword argument 'a' TypeError: test.test_extcall.f() got multiple values for keyword argument 'a'
>>> f(1, 2, a=3, **{'a': 4}, **{'a': 5}) >>> f(1, 2, a=3, **{'a': 4}, **{'a': 5})
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() got multiple values for keyword argument 'a' TypeError: test.test_extcall.f() got multiple values for keyword argument 'a'
>>> f(1, 2, 3, *[4, 5], **{'a':6, 'b':7}) >>> f(1, 2, 3, *[4, 5], **{'a':6, 'b':7})
(1, 2, 3, 4, 5) {'a': 6, 'b': 7} (1, 2, 3, 4, 5) {'a': 6, 'b': 7}
>>> f(1, 2, 3, x=4, y=5, *(6, 7), **{'a':8, 'b': 9}) >>> f(1, 2, 3, x=4, y=5, *(6, 7), **{'a':8, 'b': 9})
@ -118,7 +118,7 @@ Verify clearing of SF bug #733667
>>> g(*Nothing()) >>> g(*Nothing())
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: g() argument after * must be an iterable, not Nothing TypeError: test.test_extcall.g() argument after * must be an iterable, not Nothing
>>> class Nothing: >>> class Nothing:
... def __len__(self): return 5 ... def __len__(self): return 5
@ -127,7 +127,7 @@ Verify clearing of SF bug #733667
>>> g(*Nothing()) >>> g(*Nothing())
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: g() argument after * must be an iterable, not Nothing TypeError: test.test_extcall.g() argument after * must be an iterable, not Nothing
>>> class Nothing(): >>> class Nothing():
... def __len__(self): return 5 ... def __len__(self): return 5
@ -247,17 +247,17 @@ What about willful misconduct?
>>> h(*h) >>> h(*h)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: h() argument after * must be an iterable, not function TypeError: test.test_extcall.h() argument after * must be an iterable, not function
>>> h(1, *h) >>> h(1, *h)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: h() argument after * must be an iterable, not function TypeError: test.test_extcall.h() argument after * must be an iterable, not function
>>> h(*[1], *h) >>> h(*[1], *h)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: h() argument after * must be an iterable, not function TypeError: test.test_extcall.h() argument after * must be an iterable, not function
>>> dir(*h) >>> dir(*h)
Traceback (most recent call last): Traceback (most recent call last):
@ -268,38 +268,38 @@ What about willful misconduct?
>>> nothing(*h) >>> nothing(*h)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: NoneType object argument after * must be an iterable, \ TypeError: None argument after * must be an iterable, \
not function not function
>>> h(**h) >>> h(**h)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: h() argument after ** must be a mapping, not function TypeError: test.test_extcall.h() argument after ** must be a mapping, not function
>>> h(**[]) >>> h(**[])
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: h() argument after ** must be a mapping, not list TypeError: test.test_extcall.h() argument after ** must be a mapping, not list
>>> h(a=1, **h) >>> h(a=1, **h)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: h() argument after ** must be a mapping, not function TypeError: test.test_extcall.h() argument after ** must be a mapping, not function
>>> h(a=1, **[]) >>> h(a=1, **[])
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: h() argument after ** must be a mapping, not list TypeError: test.test_extcall.h() argument after ** must be a mapping, not list
>>> h(**{'a': 1}, **h) >>> h(**{'a': 1}, **h)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: h() argument after ** must be a mapping, not function TypeError: test.test_extcall.h() argument after ** must be a mapping, not function
>>> h(**{'a': 1}, **[]) >>> h(**{'a': 1}, **[])
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: h() argument after ** must be a mapping, not list TypeError: test.test_extcall.h() argument after ** must be a mapping, not list
>>> dir(**h) >>> dir(**h)
Traceback (most recent call last): Traceback (most recent call last):
@ -309,7 +309,7 @@ not function
>>> nothing(**h) >>> nothing(**h)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: NoneType object argument after ** must be a mapping, \ TypeError: None argument after ** must be a mapping, \
not function not function
>>> dir(b=1, **{'b': 1}) >>> dir(b=1, **{'b': 1})
@ -351,17 +351,17 @@ Test a kwargs mapping with duplicated keys.
>>> g(**MultiDict([('x', 1), ('x', 2)])) >>> g(**MultiDict([('x', 1), ('x', 2)]))
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: g() got multiple values for keyword argument 'x' TypeError: test.test_extcall.g() got multiple values for keyword argument 'x'
>>> g(a=3, **MultiDict([('x', 1), ('x', 2)])) >>> g(a=3, **MultiDict([('x', 1), ('x', 2)]))
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: g() got multiple values for keyword argument 'x' TypeError: test.test_extcall.g() got multiple values for keyword argument 'x'
>>> g(**MultiDict([('a', 3)]), **MultiDict([('x', 1), ('x', 2)])) >>> g(**MultiDict([('a', 3)]), **MultiDict([('x', 1), ('x', 2)]))
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: g() got multiple values for keyword argument 'x' TypeError: test.test_extcall.g() got multiple values for keyword argument 'x'
Another helper function Another helper function

View File

@ -236,27 +236,27 @@ Overridden parameters
>>> f(x=5, **{'x': 3}, y=2) >>> f(x=5, **{'x': 3}, y=2)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() got multiple values for keyword argument 'x' TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'
>>> f(**{'x': 3}, x=5, y=2) >>> f(**{'x': 3}, x=5, y=2)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() got multiple values for keyword argument 'x' TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'
>>> f(**{'x': 3}, **{'x': 5}, y=2) >>> f(**{'x': 3}, **{'x': 5}, y=2)
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() got multiple values for keyword argument 'x' TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'
>>> f(x=5, **{'x': 3}, **{'x': 2}) >>> f(x=5, **{'x': 3}, **{'x': 2})
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() got multiple values for keyword argument 'x' TypeError: test.test_unpack_ex.f() got multiple values for keyword argument 'x'
>>> f(**{1: 3}, **{1: 5}) >>> f(**{1: 3}, **{1: 5})
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: f() got multiple values for keyword argument '1' TypeError: test.test_unpack_ex.f() got multiple values for keyword argument '1'
Unpacking non-sequence Unpacking non-sequence

View File

@ -0,0 +1,2 @@
Add :c:func:`_PyObject_FunctionStr` to get a user-friendly string representation
of a function-like object. Patch by Jeroen Demeyer.

View File

@ -231,45 +231,38 @@ getset_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value)
* *
* First, common helpers * First, common helpers
*/ */
static const char *
get_name(PyObject *func) {
assert(PyObject_TypeCheck(func, &PyMethodDescr_Type));
return ((PyMethodDescrObject *)func)->d_method->ml_name;
}
typedef void (*funcptr)(void);
static inline int static inline int
method_check_args(PyObject *func, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) method_check_args(PyObject *func, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{ {
assert(!PyErr_Occurred()); assert(!PyErr_Occurred());
assert(PyObject_TypeCheck(func, &PyMethodDescr_Type));
if (nargs < 1) { if (nargs < 1) {
PyErr_Format(PyExc_TypeError, PyObject *funcstr = _PyObject_FunctionStr(func);
"descriptor '%.200s' of '%.100s' " if (funcstr != NULL) {
"object needs an argument", PyErr_Format(PyExc_TypeError,
get_name(func), PyDescr_TYPE(func)->tp_name); "unbound method %U needs an argument", funcstr);
Py_DECREF(funcstr);
}
return -1; return -1;
} }
PyObject *self = args[0]; PyObject *self = args[0];
if (!_PyObject_RealIsSubclass((PyObject *)Py_TYPE(self), PyObject *dummy;
(PyObject *)PyDescr_TYPE(func))) if (descr_check((PyDescrObject *)func, self, &dummy)) {
{
PyErr_Format(PyExc_TypeError,
"descriptor '%.200s' for '%.100s' objects "
"doesn't apply to a '%.100s' object",
get_name(func), PyDescr_TYPE(func)->tp_name,
Py_TYPE(self)->tp_name);
return -1; return -1;
} }
if (kwnames && PyTuple_GET_SIZE(kwnames)) { if (kwnames && PyTuple_GET_SIZE(kwnames)) {
PyErr_Format(PyExc_TypeError, PyObject *funcstr = _PyObject_FunctionStr(func);
"%.200s() takes no keyword arguments", get_name(func)); if (funcstr != NULL) {
PyErr_Format(PyExc_TypeError,
"%U takes no keyword arguments", funcstr);
Py_DECREF(funcstr);
}
return -1; return -1;
} }
return 0; return 0;
} }
typedef void (*funcptr)(void);
static inline funcptr static inline funcptr
method_enter_call(PyThreadState *tstate, PyObject *func) method_enter_call(PyThreadState *tstate, PyObject *func)
{ {
@ -387,8 +380,12 @@ method_vectorcall_NOARGS(
return NULL; return NULL;
} }
if (nargs != 1) { if (nargs != 1) {
PyErr_Format(PyExc_TypeError, PyObject *funcstr = _PyObject_FunctionStr(func);
"%.200s() takes no arguments (%zd given)", get_name(func), nargs-1); if (funcstr != NULL) {
PyErr_Format(PyExc_TypeError,
"%U takes no arguments (%zd given)", funcstr, nargs-1);
Py_DECREF(funcstr);
}
return NULL; return NULL;
} }
PyCFunction meth = (PyCFunction)method_enter_call(tstate, func); PyCFunction meth = (PyCFunction)method_enter_call(tstate, func);
@ -410,9 +407,13 @@ method_vectorcall_O(
return NULL; return NULL;
} }
if (nargs != 2) { if (nargs != 2) {
PyErr_Format(PyExc_TypeError, PyObject *funcstr = _PyObject_FunctionStr(func);
"%.200s() takes exactly one argument (%zd given)", if (funcstr != NULL) {
get_name(func), nargs-1); PyErr_Format(PyExc_TypeError,
"%U takes exactly one argument (%zd given)",
funcstr, nargs-1);
Py_DECREF(funcstr);
}
return NULL; return NULL;
} }
PyCFunction meth = (PyCFunction)method_enter_call(tstate, func); PyCFunction meth = (PyCFunction)method_enter_call(tstate, func);

View File

@ -334,15 +334,6 @@ _PyCFunction_Fini(void)
* *
* First, common helpers * First, common helpers
*/ */
static const char *
get_name(PyObject *func)
{
assert(PyCFunction_Check(func));
PyMethodDef *method = ((PyCFunctionObject *)func)->m_ml;
return method->ml_name;
}
typedef void (*funcptr)(void);
static inline int static inline int
cfunction_check_kwargs(PyThreadState *tstate, PyObject *func, PyObject *kwnames) cfunction_check_kwargs(PyThreadState *tstate, PyObject *func, PyObject *kwnames)
@ -350,13 +341,19 @@ cfunction_check_kwargs(PyThreadState *tstate, PyObject *func, PyObject *kwnames)
assert(!_PyErr_Occurred(tstate)); assert(!_PyErr_Occurred(tstate));
assert(PyCFunction_Check(func)); assert(PyCFunction_Check(func));
if (kwnames && PyTuple_GET_SIZE(kwnames)) { if (kwnames && PyTuple_GET_SIZE(kwnames)) {
_PyErr_Format(tstate, PyExc_TypeError, PyObject *funcstr = _PyObject_FunctionStr(func);
"%.200s() takes no keyword arguments", get_name(func)); if (funcstr != NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
"%U takes no keyword arguments", funcstr);
Py_DECREF(funcstr);
}
return -1; return -1;
} }
return 0; return 0;
} }
typedef void (*funcptr)(void);
static inline funcptr static inline funcptr
cfunction_enter_call(PyThreadState *tstate, PyObject *func) cfunction_enter_call(PyThreadState *tstate, PyObject *func)
{ {
@ -412,9 +409,12 @@ cfunction_vectorcall_NOARGS(
} }
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (nargs != 0) { if (nargs != 0) {
_PyErr_Format(tstate, PyExc_TypeError, PyObject *funcstr = _PyObject_FunctionStr(func);
"%.200s() takes no arguments (%zd given)", if (funcstr != NULL) {
get_name(func), nargs); _PyErr_Format(tstate, PyExc_TypeError,
"%U takes no arguments (%zd given)", funcstr, nargs);
Py_DECREF(funcstr);
}
return NULL; return NULL;
} }
PyCFunction meth = (PyCFunction)cfunction_enter_call(tstate, func); PyCFunction meth = (PyCFunction)cfunction_enter_call(tstate, func);
@ -436,9 +436,12 @@ cfunction_vectorcall_O(
} }
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
if (nargs != 1) { if (nargs != 1) {
_PyErr_Format(tstate, PyExc_TypeError, PyObject *funcstr = _PyObject_FunctionStr(func);
"%.200s() takes exactly one argument (%zd given)", if (funcstr != NULL) {
get_name(func), nargs); _PyErr_Format(tstate, PyExc_TypeError,
"%U takes exactly one argument (%zd given)", funcstr, nargs);
Py_DECREF(funcstr);
}
return NULL; return NULL;
} }
PyCFunction meth = (PyCFunction)cfunction_enter_call(tstate, func); PyCFunction meth = (PyCFunction)cfunction_enter_call(tstate, func);

View File

@ -681,6 +681,64 @@ PyObject_Bytes(PyObject *v)
return PyBytes_FromObject(v); return PyBytes_FromObject(v);
} }
/*
def _PyObject_FunctionStr(x):
try:
qualname = x.__qualname__
except AttributeError:
return str(x)
try:
mod = x.__module__
if mod is not None and mod != 'builtins':
return f"{x.__module__}.{qualname}()"
except AttributeError:
pass
return qualname
*/
PyObject *
_PyObject_FunctionStr(PyObject *x)
{
_Py_IDENTIFIER(__module__);
_Py_IDENTIFIER(__qualname__);
_Py_IDENTIFIER(builtins);
assert(!PyErr_Occurred());
PyObject *qualname;
int ret = _PyObject_LookupAttrId(x, &PyId___qualname__, &qualname);
if (qualname == NULL) {
if (ret < 0) {
return NULL;
}
return PyObject_Str(x);
}
PyObject *module;
PyObject *result = NULL;
ret = _PyObject_LookupAttrId(x, &PyId___module__, &module);
if (module != NULL && module != Py_None) {
PyObject *builtinsname = _PyUnicode_FromId(&PyId_builtins);
if (builtinsname == NULL) {
goto done;
}
ret = PyObject_RichCompareBool(module, builtinsname, Py_NE);
if (ret < 0) {
// error
goto done;
}
if (ret > 0) {
result = PyUnicode_FromFormat("%S.%S()", module, qualname);
goto done;
}
}
else if (ret < 0) {
goto done;
}
result = PyUnicode_FromFormat("%S()", qualname);
done:
Py_DECREF(qualname);
Py_XDECREF(module);
return result;
}
/* For Python 3.0.1 and later, the old three-way comparison has been /* For Python 3.0.1 and later, the old three-way comparison has been
completely removed in favour of rich comparisons. PyObject_Compare() and completely removed in favour of rich comparisons. PyObject_Compare() and
PyObject_Cmp() are gone, and the builtin cmp function no longer exists. PyObject_Cmp() are gone, and the builtin cmp function no longer exists.

View File

@ -5351,12 +5351,17 @@ static int
check_args_iterable(PyThreadState *tstate, PyObject *func, PyObject *args) check_args_iterable(PyThreadState *tstate, PyObject *func, PyObject *args)
{ {
if (args->ob_type->tp_iter == NULL && !PySequence_Check(args)) { if (args->ob_type->tp_iter == NULL && !PySequence_Check(args)) {
_PyErr_Format(tstate, PyExc_TypeError, /* check_args_iterable() may be called with a live exception:
"%.200s%.200s argument after * " * clear it to prevent calling _PyObject_FunctionStr() with an
"must be an iterable, not %.200s", * exception set. */
PyEval_GetFuncName(func), PyErr_Clear();
PyEval_GetFuncDesc(func), PyObject *funcstr = _PyObject_FunctionStr(func);
args->ob_type->tp_name); if (funcstr != NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
"%U argument after * must be an iterable, not %.200s",
funcstr, Py_TYPE(args)->tp_name);
Py_DECREF(funcstr);
}
return -1; return -1;
} }
return 0; return 0;
@ -5372,24 +5377,30 @@ format_kwargs_error(PyThreadState *tstate, PyObject *func, PyObject *kwargs)
* is not a mapping. * is not a mapping.
*/ */
if (_PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) { if (_PyErr_ExceptionMatches(tstate, PyExc_AttributeError)) {
_PyErr_Format(tstate, PyExc_TypeError, PyErr_Clear();
"%.200s%.200s argument after ** " PyObject *funcstr = _PyObject_FunctionStr(func);
"must be a mapping, not %.200s", if (funcstr != NULL) {
PyEval_GetFuncName(func), _PyErr_Format(
PyEval_GetFuncDesc(func), tstate, PyExc_TypeError,
kwargs->ob_type->tp_name); "%U argument after ** must be a mapping, not %.200s",
funcstr, Py_TYPE(kwargs)->tp_name);
Py_DECREF(funcstr);
}
} }
else if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) { else if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
PyObject *exc, *val, *tb; PyObject *exc, *val, *tb;
_PyErr_Fetch(tstate, &exc, &val, &tb); _PyErr_Fetch(tstate, &exc, &val, &tb);
if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) { if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) {
PyObject *key = PyTuple_GET_ITEM(val, 0); PyErr_Clear();
_PyErr_Format(tstate, PyExc_TypeError, PyObject *funcstr = _PyObject_FunctionStr(func);
"%.200s%.200s got multiple " if (funcstr != NULL) {
"values for keyword argument '%S'", PyObject *key = PyTuple_GET_ITEM(val, 0);
PyEval_GetFuncName(func), _PyErr_Format(
PyEval_GetFuncDesc(func), tstate, PyExc_TypeError,
key); "%U got multiple values for keyword argument '%S'",
funcstr, key);
Py_DECREF(funcstr);
}
Py_XDECREF(exc); Py_XDECREF(exc);
Py_XDECREF(val); Py_XDECREF(val);
Py_XDECREF(tb); Py_XDECREF(tb);