Skip to content

Commit f1ec3ce

Browse files
bpo-35634: Raise an error when first passed kwargs contains duplicated keys. (GH-11438)
1 parent 58159ef commit f1ec3ce

File tree

3 files changed

+94
-48
lines changed

3 files changed

+94
-48
lines changed

Lib/test/test_extcall.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,52 @@
316316
...
317317
TypeError: dir() got multiple values for keyword argument 'b'
318318
319+
Test a kwargs mapping with duplicated keys.
320+
321+
>>> from collections.abc import Mapping
322+
>>> class MultiDict(Mapping):
323+
... def __init__(self, items):
324+
... self._items = items
325+
...
326+
... def __iter__(self):
327+
... return (k for k, v in self._items)
328+
...
329+
... def __getitem__(self, key):
330+
... for k, v in self._items:
331+
... if k == key:
332+
... return v
333+
... raise KeyError(key)
334+
...
335+
... def __len__(self):
336+
... return len(self._items)
337+
...
338+
... def keys(self):
339+
... return [k for k, v in self._items]
340+
...
341+
... def values(self):
342+
... return [v for k, v in self._items]
343+
...
344+
... def items(self):
345+
... return [(k, v) for k, v in self._items]
346+
...
347+
>>> g(**MultiDict([('x', 1), ('y', 2)]))
348+
1 () {'y': 2}
349+
350+
>>> g(**MultiDict([('x', 1), ('x', 2)]))
351+
Traceback (most recent call last):
352+
...
353+
TypeError: g() got multiple values for keyword argument 'x'
354+
355+
>>> g(a=3, **MultiDict([('x', 1), ('x', 2)]))
356+
Traceback (most recent call last):
357+
...
358+
TypeError: g() got multiple values for keyword argument 'x'
359+
360+
>>> g(**MultiDict([('a', 3)]), **MultiDict([('x', 1), ('x', 2)]))
361+
Traceback (most recent call last):
362+
...
363+
TypeError: g() got multiple values for keyword argument 'x'
364+
319365
Another helper function
320366
321367
>>> def f2(*a, **b):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``func(**kwargs)`` will now raise an error when ``kwargs`` is a mapping
2+
containing multiple entries with the same key. An error was already raised
3+
when other keyword arguments are passed before ``**kwargs`` since Python 3.6.

Python/ceval.c

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ static PyObject * unicode_concatenate(PyObject *, PyObject *,
7070
PyFrameObject *, const _Py_CODEUNIT *);
7171
static PyObject * special_lookup(PyObject *, _Py_Identifier *);
7272
static int check_args_iterable(PyObject *func, PyObject *vararg);
73-
static void format_kwargs_mapping_error(PyObject *func, PyObject *kwargs);
73+
static void format_kwargs_error(PyObject *func, PyObject *kwargs);
7474
static void format_awaitable_error(PyTypeObject *, int);
7575

7676
#define NAME_ERROR_MSG \
@@ -2660,37 +2660,8 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
26602660
for (i = oparg; i > 0; i--) {
26612661
PyObject *arg = PEEK(i);
26622662
if (_PyDict_MergeEx(sum, arg, 2) < 0) {
2663-
PyObject *func = PEEK(2 + oparg);
2664-
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
2665-
format_kwargs_mapping_error(func, arg);
2666-
}
2667-
else if (PyErr_ExceptionMatches(PyExc_KeyError)) {
2668-
PyObject *exc, *val, *tb;
2669-
PyErr_Fetch(&exc, &val, &tb);
2670-
if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) {
2671-
PyObject *key = PyTuple_GET_ITEM(val, 0);
2672-
if (!PyUnicode_Check(key)) {
2673-
PyErr_Format(PyExc_TypeError,
2674-
"%.200s%.200s keywords must be strings",
2675-
PyEval_GetFuncName(func),
2676-
PyEval_GetFuncDesc(func));
2677-
} else {
2678-
PyErr_Format(PyExc_TypeError,
2679-
"%.200s%.200s got multiple "
2680-
"values for keyword argument '%U'",
2681-
PyEval_GetFuncName(func),
2682-
PyEval_GetFuncDesc(func),
2683-
key);
2684-
}
2685-
Py_XDECREF(exc);
2686-
Py_XDECREF(val);
2687-
Py_XDECREF(tb);
2688-
}
2689-
else {
2690-
PyErr_Restore(exc, val, tb);
2691-
}
2692-
}
26932663
Py_DECREF(sum);
2664+
format_kwargs_error(PEEK(2 + oparg), arg);
26942665
goto error;
26952666
}
26962667
}
@@ -3286,17 +3257,9 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
32863257
PyObject *d = PyDict_New();
32873258
if (d == NULL)
32883259
goto error;
3289-
if (PyDict_Update(d, kwargs) != 0) {
3260+
if (_PyDict_MergeEx(d, kwargs, 2) < 0) {
32903261
Py_DECREF(d);
3291-
/* PyDict_Update raises attribute
3292-
* error (percolated from an attempt
3293-
* to get 'keys' attribute) instead of
3294-
* a type error if its second argument
3295-
* is not a mapping.
3296-
*/
3297-
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
3298-
format_kwargs_mapping_error(SECOND(), kwargs);
3299-
}
3262+
format_kwargs_error(SECOND(), kwargs);
33003263
Py_DECREF(kwargs);
33013264
goto error;
33023265
}
@@ -5063,14 +5026,48 @@ check_args_iterable(PyObject *func, PyObject *args)
50635026
}
50645027

50655028
static void
5066-
format_kwargs_mapping_error(PyObject *func, PyObject *kwargs)
5029+
format_kwargs_error(PyObject *func, PyObject *kwargs)
50675030
{
5068-
PyErr_Format(PyExc_TypeError,
5069-
"%.200s%.200s argument after ** "
5070-
"must be a mapping, not %.200s",
5071-
PyEval_GetFuncName(func),
5072-
PyEval_GetFuncDesc(func),
5073-
kwargs->ob_type->tp_name);
5031+
/* _PyDict_MergeEx raises attribute
5032+
* error (percolated from an attempt
5033+
* to get 'keys' attribute) instead of
5034+
* a type error if its second argument
5035+
* is not a mapping.
5036+
*/
5037+
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
5038+
PyErr_Format(PyExc_TypeError,
5039+
"%.200s%.200s argument after ** "
5040+
"must be a mapping, not %.200s",
5041+
PyEval_GetFuncName(func),
5042+
PyEval_GetFuncDesc(func),
5043+
kwargs->ob_type->tp_name);
5044+
}
5045+
else if (PyErr_ExceptionMatches(PyExc_KeyError)) {
5046+
PyObject *exc, *val, *tb;
5047+
PyErr_Fetch(&exc, &val, &tb);
5048+
if (val && PyTuple_Check(val) && PyTuple_GET_SIZE(val) == 1) {
5049+
PyObject *key = PyTuple_GET_ITEM(val, 0);
5050+
if (!PyUnicode_Check(key)) {
5051+
PyErr_Format(PyExc_TypeError,
5052+
"%.200s%.200s keywords must be strings",
5053+
PyEval_GetFuncName(func),
5054+
PyEval_GetFuncDesc(func));
5055+
} else {
5056+
PyErr_Format(PyExc_TypeError,
5057+
"%.200s%.200s got multiple "
5058+
"values for keyword argument '%U'",
5059+
PyEval_GetFuncName(func),
5060+
PyEval_GetFuncDesc(func),
5061+
key);
5062+
}
5063+
Py_XDECREF(exc);
5064+
Py_XDECREF(val);
5065+
Py_XDECREF(tb);
5066+
}
5067+
else {
5068+
PyErr_Restore(exc, val, tb);
5069+
}
5070+
}
50745071
}
50755072

50765073
static void

0 commit comments

Comments
 (0)