Skip to content

Commit 9e4f2f3

Browse files
authored
bpo-20180: Use argument clinic for dict.pop() and dict.popitem() (GH-12792)
1 parent 3993ccb commit 9e4f2f3

File tree

3 files changed

+101
-35
lines changed

3 files changed

+101
-35
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
``dict.pop()`` is now up to 33% faster thanks to Argument Clinic. Patch by
2+
Inada Naoki.

Objects/clinic/dictobject.c.h

Lines changed: 58 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/dictobject.c

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2985,19 +2985,37 @@ dict_clear(PyDictObject *mp, PyObject *Py_UNUSED(ignored))
29852985
Py_RETURN_NONE;
29862986
}
29872987

2988+
/*[clinic input]
2989+
dict.pop
2990+
2991+
key: object
2992+
default: object = NULL
2993+
/
2994+
2995+
Remove specified key and return the corresponding value.
2996+
2997+
If key is not found, default is returned if given, otherwise KeyError is raised
2998+
[clinic start generated code]*/
2999+
29883000
static PyObject *
2989-
dict_pop(PyDictObject *mp, PyObject *args)
3001+
dict_pop_impl(PyDictObject *self, PyObject *key, PyObject *default_value)
3002+
/*[clinic end generated code: output=3abb47b89f24c21c input=016f6a000e4e633b]*/
29903003
{
2991-
PyObject *key, *deflt = NULL;
3004+
return _PyDict_Pop((PyObject*)self, key, default_value);
3005+
}
29923006

2993-
if(!PyArg_UnpackTuple(args, "pop", 1, 2, &key, &deflt))
2994-
return NULL;
3007+
/*[clinic input]
3008+
dict.popitem
29953009
2996-
return _PyDict_Pop((PyObject*)mp, key, deflt);
2997-
}
3010+
Remove and return a (key, value) pair as a 2-tuple.
3011+
3012+
Pairs are returned in LIFO (last-in, first-out) order.
3013+
Raises KeyError if the dict is empty.
3014+
[clinic start generated code]*/
29983015

29993016
static PyObject *
3000-
dict_popitem(PyDictObject *mp, PyObject *Py_UNUSED(ignored))
3017+
dict_popitem_impl(PyDictObject *self)
3018+
/*[clinic end generated code: output=e65fcb04420d230d input=1c38a49f21f64941]*/
30013019
{
30023020
Py_ssize_t i, j;
30033021
PyDictKeyEntry *ep0, *ep;
@@ -3015,44 +3033,43 @@ dict_popitem(PyDictObject *mp, PyObject *Py_UNUSED(ignored))
30153033
res = PyTuple_New(2);
30163034
if (res == NULL)
30173035
return NULL;
3018-
if (mp->ma_used == 0) {
3036+
if (self->ma_used == 0) {
30193037
Py_DECREF(res);
3020-
PyErr_SetString(PyExc_KeyError,
3021-
"popitem(): dictionary is empty");
3038+
PyErr_SetString(PyExc_KeyError, "popitem(): dictionary is empty");
30223039
return NULL;
30233040
}
30243041
/* Convert split table to combined table */
3025-
if (mp->ma_keys->dk_lookup == lookdict_split) {
3026-
if (dictresize(mp, DK_SIZE(mp->ma_keys))) {
3042+
if (self->ma_keys->dk_lookup == lookdict_split) {
3043+
if (dictresize(self, DK_SIZE(self->ma_keys))) {
30273044
Py_DECREF(res);
30283045
return NULL;
30293046
}
30303047
}
3031-
ENSURE_ALLOWS_DELETIONS(mp);
3048+
ENSURE_ALLOWS_DELETIONS(self);
30323049

30333050
/* Pop last item */
3034-
ep0 = DK_ENTRIES(mp->ma_keys);
3035-
i = mp->ma_keys->dk_nentries - 1;
3051+
ep0 = DK_ENTRIES(self->ma_keys);
3052+
i = self->ma_keys->dk_nentries - 1;
30363053
while (i >= 0 && ep0[i].me_value == NULL) {
30373054
i--;
30383055
}
30393056
assert(i >= 0);
30403057

30413058
ep = &ep0[i];
3042-
j = lookdict_index(mp->ma_keys, ep->me_hash, i);
3059+
j = lookdict_index(self->ma_keys, ep->me_hash, i);
30433060
assert(j >= 0);
3044-
assert(dictkeys_get_index(mp->ma_keys, j) == i);
3045-
dictkeys_set_index(mp->ma_keys, j, DKIX_DUMMY);
3061+
assert(dictkeys_get_index(self->ma_keys, j) == i);
3062+
dictkeys_set_index(self->ma_keys, j, DKIX_DUMMY);
30463063

30473064
PyTuple_SET_ITEM(res, 0, ep->me_key);
30483065
PyTuple_SET_ITEM(res, 1, ep->me_value);
30493066
ep->me_key = NULL;
30503067
ep->me_value = NULL;
30513068
/* We can't dk_usable++ since there is DKIX_DUMMY in indices */
3052-
mp->ma_keys->dk_nentries = i;
3053-
mp->ma_used--;
3054-
mp->ma_version_tag = DICT_NEXT_VERSION();
3055-
assert(_PyDict_CheckConsistency(mp));
3069+
self->ma_keys->dk_nentries = i;
3070+
self->ma_used--;
3071+
self->ma_version_tag = DICT_NEXT_VERSION();
3072+
assert(_PyDict_CheckConsistency(self));
30563073
return res;
30573074
}
30583075

@@ -3135,14 +3152,6 @@ PyDoc_STRVAR(getitem__doc__, "x.__getitem__(y) <==> x[y]");
31353152
PyDoc_STRVAR(sizeof__doc__,
31363153
"D.__sizeof__() -> size of D in memory, in bytes");
31373154

3138-
PyDoc_STRVAR(pop__doc__,
3139-
"D.pop(k[,d]) -> v, remove specified key and return the corresponding value.\n\
3140-
If key is not found, d is returned if given, otherwise KeyError is raised");
3141-
3142-
PyDoc_STRVAR(popitem__doc__,
3143-
"D.popitem() -> (k, v), remove and return some (key, value) pair as a\n\
3144-
2-tuple; but raise KeyError if D is empty.");
3145-
31463155
PyDoc_STRVAR(update__doc__,
31473156
"D.update([E, ]**F) -> None. Update D from dict/iterable E and F.\n\
31483157
If E is present and has a .keys() method, then does: for k in E: D[k] = E[k]\n\
@@ -3175,10 +3184,8 @@ static PyMethodDef mapp_methods[] = {
31753184
sizeof__doc__},
31763185
DICT_GET_METHODDEF
31773186
DICT_SETDEFAULT_METHODDEF
3178-
{"pop", (PyCFunction)dict_pop, METH_VARARGS,
3179-
pop__doc__},
3180-
{"popitem", (PyCFunction)(void(*)(void))dict_popitem, METH_NOARGS,
3181-
popitem__doc__},
3187+
DICT_POP_METHODDEF
3188+
DICT_POPITEM_METHODDEF
31823189
{"keys", dictkeys_new, METH_NOARGS,
31833190
keys__doc__},
31843191
{"items", dictitems_new, METH_NOARGS,

0 commit comments

Comments
 (0)