Skip to content

Commit df4444a

Browse files
committed
bpo-29312: use METH_FASTCALL for dict.update()
1 parent 0567786 commit df4444a

File tree

2 files changed

+98
-12
lines changed

2 files changed

+98
-12
lines changed

Lib/test/test_dict.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,11 @@ def test_update(self):
142142
d.update({2:20})
143143
d.update({1:1, 2:2, 3:3})
144144
self.assertEqual(d, {1:1, 2:2, 3:3})
145+
d.update(([i,i] for i in range(4,6)), key="value")
146+
self.assertEqual(d, {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 'key': 'value'})
145147

146148
d.update()
147-
self.assertEqual(d, {1:1, 2:2, 3:3})
149+
self.assertEqual(d, {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 'key': 'value'})
148150

149151
self.assertRaises((TypeError, AttributeError), d.update, None)
150152

Objects/dictobject.c

Lines changed: 95 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2340,16 +2340,6 @@ dict_update_common(PyObject *self, PyObject *args, PyObject *kwds,
23402340
return result;
23412341
}
23422342

2343-
/* Note: dict.update() uses the METH_VARARGS|METH_KEYWORDS calling convention.
2344-
Using METH_FASTCALL|METH_KEYWORDS would make dict.update(**dict2) calls
2345-
slower, see the issue #29312. */
2346-
static PyObject *
2347-
dict_update(PyObject *self, PyObject *args, PyObject *kwds)
2348-
{
2349-
if (dict_update_common(self, args, kwds, "update") != -1)
2350-
Py_RETURN_NONE;
2351-
return NULL;
2352-
}
23532343

23542344
/* Update unconditionally replaces existing items.
23552345
Merge has a 3rd argument 'override'; if set, it acts like Update,
@@ -2591,6 +2581,100 @@ dict_merge(PyObject *a, PyObject *b, int override)
25912581
return 0;
25922582
}
25932583

2584+
/* Implementation of d.update(obj). There are two cases:
2585+
* - if obj has a "keys" method, assume that it is a mapping
2586+
* - otherwise, obj must be an iterable of (key, value) pairs
2587+
*/
2588+
static int
2589+
dict_merge_from_any(PyObject *self, PyObject *obj)
2590+
{
2591+
/* Fast path for dict subclasses */
2592+
if (PyDict_Check(obj)) {
2593+
return dict_merge(self, obj, 1);
2594+
}
2595+
2596+
_Py_IDENTIFIER(keys);
2597+
PyObject *func;
2598+
if (_PyObject_LookupAttrId(obj, &PyId_keys, &func) < 0) {
2599+
return -1;
2600+
}
2601+
if (func != NULL) {
2602+
Py_DECREF(func);
2603+
return dict_merge(self, obj, 1);
2604+
}
2605+
else {
2606+
return PyDict_MergeFromSeq2(self, obj, 1);
2607+
}
2608+
}
2609+
2610+
/* Insert the key:value pairs from the arrays keys and values (of length n).
2611+
* Return 0 if successful, -1 if an exception was raised */
2612+
static int
2613+
dict_merge_from_arrays(PyDictObject *mp, PyObject *const *keys, PyObject *const *values, Py_ssize_t n)
2614+
{
2615+
/* Do one big resize at the start, rather than
2616+
* incrementally resizing as we insert new items. Expect
2617+
* that there will be no (or few) overlapping keys.
2618+
*/
2619+
if (USABLE_FRACTION(mp->ma_keys->dk_size) < n) {
2620+
if (dictresize(mp, ESTIMATE_SIZE(mp->ma_used + n))) {
2621+
return -1;
2622+
}
2623+
}
2624+
2625+
for (Py_ssize_t i = 0; i < n; i++) {
2626+
PyObject *key = keys[i];
2627+
PyObject *value = values[i];
2628+
assert(PyUnicode_Check(key));
2629+
Py_hash_t hash = ((PyASCIIObject *)key)->hash;
2630+
if (hash == -1) {
2631+
hash = PyObject_Hash(key);
2632+
if (hash == -1) {
2633+
return -1;
2634+
}
2635+
}
2636+
if (insertdict(mp, key, hash, value)) {
2637+
return -1;
2638+
}
2639+
}
2640+
ASSERT_CONSISTENT(mp);
2641+
return 0;
2642+
}
2643+
2644+
2645+
static int
2646+
dict_update_impl(PyObject *self, PyObject *const *args,
2647+
Py_ssize_t nargs, PyObject *kwnames,
2648+
const char *methname)
2649+
{
2650+
if (nargs) {
2651+
if (!_PyArg_CheckPositional(methname, nargs, 0, 1)) {
2652+
return -1;
2653+
}
2654+
if (dict_merge_from_any(self, args[0])) {
2655+
return -1;
2656+
}
2657+
args += 1;
2658+
}
2659+
if (kwnames) {
2660+
Py_ssize_t nkw = PyTuple_GET_SIZE(kwnames);
2661+
PyObject *const *names = &PyTuple_GET_ITEM(kwnames, 0);
2662+
return dict_merge_from_arrays((PyDictObject*)self, names, args, nkw);
2663+
}
2664+
return 0;
2665+
}
2666+
2667+
static PyObject *
2668+
dict_update(PyObject *self, PyObject *const *args,
2669+
Py_ssize_t nargs, PyObject *kwnames)
2670+
{
2671+
if (dict_update_impl(self, args, nargs, kwnames, "update")) {
2672+
return NULL;
2673+
}
2674+
Py_RETURN_NONE;
2675+
}
2676+
2677+
25942678
int
25952679
PyDict_Update(PyObject *a, PyObject *b)
25962680
{
@@ -3193,7 +3277,7 @@ static PyMethodDef mapp_methods[] = {
31933277
items__doc__},
31943278
{"values", dictvalues_new, METH_NOARGS,
31953279
values__doc__},
3196-
{"update", (PyCFunction)(void(*)(void))dict_update, METH_VARARGS | METH_KEYWORDS,
3280+
{"update", (PyCFunction)(void(*)(void))dict_update, METH_FASTCALL | METH_KEYWORDS,
31973281
update__doc__},
31983282
DICT_FROMKEYS_METHODDEF
31993283
{"clear", (PyCFunction)dict_clear, METH_NOARGS,

0 commit comments

Comments
 (0)