From 45de5b2f81d6ef5ba0a45c0792403c230653ff1b Mon Sep 17 00:00:00 2001 From: Cloud User Date: Sat, 19 Jul 2025 07:53:07 -0400 Subject: [PATCH] Adding deepget method to dictionary --- Include/dictobject.h | 1 - Objects/clinic/dictobject.c.h | 40 +++++++++++++++++++++++++++++++++++ Objects/dictobject.c | 35 ++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/Include/dictobject.h b/Include/dictobject.h index 1bbeec1ab699e7..cfd286f9ce941c 100644 --- a/Include/dictobject.h +++ b/Include/dictobject.h @@ -17,7 +17,6 @@ PyAPI_DATA(PyTypeObject) PyDict_Type; #define PyDict_Check(op) \ PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_DICT_SUBCLASS) #define PyDict_CheckExact(op) Py_IS_TYPE((op), &PyDict_Type) - PyAPI_FUNC(PyObject *) PyDict_New(void); PyAPI_FUNC(PyObject *) PyDict_GetItem(PyObject *mp, PyObject *key); PyAPI_FUNC(PyObject *) PyDict_GetItemWithError(PyObject *mp, PyObject *key); diff --git a/Objects/clinic/dictobject.c.h b/Objects/clinic/dictobject.c.h index abf6b38449fcb0..0cf5d44d9a0fd0 100644 --- a/Objects/clinic/dictobject.c.h +++ b/Objects/clinic/dictobject.c.h @@ -113,6 +113,46 @@ dict_get(PyObject *self, PyObject *const *args, Py_ssize_t nargs) return return_value; } +PyDoc_STRVAR(dict_deepget__doc__, +"deepget($self, keys, default=None, /)\n" +"--\n" +"\n" +"Return the value for the nested keys if the full path exists in the dictionary, else return default."); + +#define DICT_DEEPGET_METHODDEF \ + {"deepget", _PyCFunction_CAST(dict_deepget), METH_FASTCALL, dict_deepget__doc__}, + +static PyObject * +dict_deepget_impl(PyDictObject *self, PyObject * const *keylist, PyObject *default_value); + +static PyObject * +dict_deepget(PyObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *keylist; + PyObject *default_value = Py_None; + + if (!_PyArg_CheckPositional("deepget", nargs, 1, 2)) { + goto exit; + } + + keylist = args[0]; + if (!PyList_Check(keylist)) { + PyErr_SetString(PyExc_TypeError, "keys must be provided as a list"); + return NULL; + } + + if (nargs < 2) { + goto skip_optional; + } + default_value = args[1]; +skip_optional: + return_value = dict_deepget_impl((PyDictObject *)self, keylist, default_value); + +exit: + return return_value; +} + PyDoc_STRVAR(dict_setdefault__doc__, "setdefault($self, key, default=None, /)\n" "--\n" diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 0ed52ac5e87b6e..57acfda7e6cd3f 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -4312,6 +4312,40 @@ dict_get_impl(PyDictObject *self, PyObject *key, PyObject *default_value) return val; } +static PyObject * +dict_deepget_impl(PyDictObject *self, PyObject * const *keylist, PyObject *default_value) +{ + Py_ssize_t n = PyList_GET_SIZE(keylist); + if (n == 0) { + return default_value; + } + + PyObject *val = (PyObject *)self; // Start with top-level dict + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *key = PyList_GET_ITEM(keylist, i); + + if (!PyDict_Check(val)) { + return default_value; + } + + Py_hash_t hash = _PyObject_HashFast(key); + if (hash == -1) { + dict_unhashable_type(key); + return default_value; + } + + PyObject *next = NULL; + Py_ssize_t ix = _Py_dict_lookup_threadsafe((PyDictObject *)val, key, hash, &next); + if (ix == DKIX_ERROR || ix == DKIX_EMPTY || next == NULL) { + return Py_NewRef(default_value); + } + + val = next; + } + + return Py_NewRef(val); +} + static int dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_value, PyObject **result, int incref_result) @@ -4741,6 +4775,7 @@ static PyMethodDef mapp_methods[] = { getitem__doc__}, DICT___SIZEOF___METHODDEF DICT_GET_METHODDEF + DICT_DEEPGET_METHODDEF DICT_SETDEFAULT_METHODDEF DICT_POP_METHODDEF DICT_POPITEM_METHODDEF