8000 gh-111262: Add PyDict_Pop() function · vstinner/cpython@1498269 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1498269

Browse files
vstinnerscoderpitrou
committed
pythongh-111262: Add PyDict_Pop() function
_PyDict_Pop_KnownHash(): remove the default value and the return type becomes an int. Co-Authored-By: Stefan Behnel <stefan_ml@behnel.de> Co-authored-by: Antoine Pitrou <pitrou@free.fr>
1 parent 324531d commit 1498269

File tree

19 files changed

+198
-67
lines changed
  • Doc
    • c-api
  • data
  • whatsnew
  • Include
  • Lib/test
  • Misc
  • Modules
  • Objects
  • PC
  • Python
  • 19 files changed

    +198
    -67
    lines changed

    Doc/c-api/dict.rst

    Lines changed: 17 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -173,6 +173,23 @@ Dictionary Objects
    173173
    174174
    .. versionadded:: 3.4
    175175
    176+
    177+
    .. c:function:: int PyDict_Pop(PyObject *p, PyObject *key, PyObject **result)
    178+
    179+
    Remove *key* from dictionary *p* and optionally return the removed value.
    180+
    Do not raise :exc:`KeyError` if the key missing.
    181+
    182+
    - If the key is present, set *\*result* to a new reference to the removed
    183+
    value if *result* is not ``NULL``, and return ``1``.
    184+
    - If the key is missing, set *\*result* to ``NULL`` if *result* is not
    185+
    ``NULL``, and return ``0``.
    186+
    - On error, raise an exception and return ``-1``.
    187+
    188+
    This is the similar to :meth:`dict.pop`, but without the default value and
    189+
    do not raise :exc:`KeyError` if the key missing.
    190+
    191+
    .. versionadded:: 3.13
    192+
    176193
    .. c:function:: PyObject* PyDict_Items(PyObject *p)
    177194
    178195
    Return a :c:type:`PyListObject` containing all the items from the dictionary.

    Doc/data/stable_abi.dat

    Lines changed: 1 addition & 0 deletions
    Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

    Doc/whatsnew/3.13.rst

    Lines changed: 6 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1175,6 +1175,12 @@ New Features
    11751175
    Python ``list.extend()`` and ``list.clear()`` methods.
    11761176
    (Contributed by Victor Stinner in :gh:`111138`.)
    11771177

    1178+
    * Add :c:func:`PyDict_Pop` function: remove a key from a dictionary and
    1179+
    optionally return the removed value. This is the similar to :meth:`dict.pop`,
    1180+
    but without the default value and do not raise :exc:`KeyError` if the key
    1181+
    missing.
    1182+
    (Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
    1183+
    11781184

    11791185
    Porting to Python 3.13
    11801186
    ----------------------

    Include/dictobject.h

    Lines changed: 2 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -66,6 +66,8 @@ PyAPI_FUNC(int) PyDict_DelItemString(PyObject *dp, const char *key);
    6666
    // - On error, raise an exception and return -1.
    6767
    PyAPI_FUNC(int) PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result);
    6868
    PyAPI_FUNC(int) PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result);
    69+
    70+
    PyAPI_FUNC(int) PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result);
    6971
    #endif
    7072

    7173
    #if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030A0000

    Include/internal/pycore_dict.h

    Lines changed: 5 additions & 1 deletion
    Original file line numberDiff line numberDiff line change
    @@ -116,7 +116,11 @@ extern PyObject *_PyDict_LoadGlobal(PyDictObject *, PyDictObject *, PyObject *);
    116116
    extern int _PyDict_SetItem_Take2(PyDictObject *op, PyObject *key, PyObject *value);
    117117
    extern int _PyObjectDict_SetItem(PyTypeObject *tp, PyObject **dictptr, PyObject *name, PyObject *value);
    118118

    119-
    extern PyObject *_PyDict_Pop_KnownHash(PyObject *, PyObject *, Py_hash_t, PyObject *);
    119+
    extern int _PyDict_Pop_KnownHash(
    120+
    PyDictObject *dict,
    121+
    PyObject *key,
    122+
    Py_hash_t hash,
    123+
    PyObject **result);
    120124

    121125
    #define DKIX_EMPTY (-1)
    122126
    #define DKIX_DUMMY (-2) /* Used internally */

    Lib/test/test_capi/test_dict.py

    Lines changed: 29 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -432,6 +432,35 @@ def test_dict_mergefromseq2(self):
    432432
    # CRASHES mergefromseq2({}, NULL, 0)
    433433
    # CRASHES mergefromseq2(NULL, {}, 0)
    434434

    435+
    def test_dict_pop(self):
    436+
    # Test PyDict_Pop()
    437+
    dict_pop = _testcapi.dict_pop
    438+
    439+
    # key present
    440+
    mydict = {"key": "value", "key2": "value2"}
    441+
    self.assertEqual(dict_pop(mydict, "key"), (1, "value"))
    442+
    self.assertEqual(mydict, {"key2": "value2"})
    443+
    self.assertEqual(dict_pop(mydict, "key2"), (1, "value2"))
    444+
    self.assertEqual(mydict, {})
    445+
    446+
    # key missing; empty dict has a fast path
    447+
    self.assertEqual(dict_pop({}, "key"), (0, None))
    448+
    self.assertEqual(dict_pop({"a": 1}, "key"), (0, None))
    449+
    450+
    # dict error
    451+
    not_dict = "string"
    452+
    self.assertRaises(SystemError, dict_pop, not_dict, "key")
    453+
    454+
    # key error; don't hash key if dict is empty
    455+
    not_hashable_key = ["list"]
    456+
    self.assertEqual(dict_pop({}, not_hashable_key), (0, None))
    457+
    with self.assertRaises(TypeError):
    458+
    dict_pop({'key': 1}, not_hashable_key)
    459+
    dict_pop({}, NULL) # key is not checked if dict is empty
    460+
    461+
    # CRASHES dict_pop(NULL, "key")
    462+
    # CRASHES dict_pop({"a": 1}, NULL, default)
    463+
    435464

    436465
    if __name__ == "__main__":
    437466
    unittest.main()

    Lib/test/test_stable_abi_ctypes.py

    Lines changed: 1 addition & 0 deletions
    Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
    Lines changed: 4 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -0,0 +1,4 @@
    1+
    Add :c:func:`PyDict_Pop` function: remove a key from a dictionary and
    2+
    optionally return the removed value. This is the similar to :meth:`dict.pop`,
    3+
    but without the default value and do not raise :exc:`KeyError` if the key
    4+
    missing. Patch by Stefan Behnel and Victor Stinner.

    Misc/stable_abi.toml

    Lines changed: 2 additions & 0 deletions
    Original file line numberDiff line numberDiff line change
    @@ -2481,3 +2481,5 @@
    24812481
    [function._Py_SetRefcnt]
    24822482
    added = '3.13'
    24832483
    abi_only = true
    2484+
    [function.PyDict_Pop]
    2485+
    added = '3.13'

    Modules/_functoolsmodule.c

    Lines changed: 15 additions & 13 deletions
    Original file line numberDiff line numberDiff line change
    @@ -1087,19 +1087,9 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
    10871087
    The cache dict holds one reference to the link.
    10881088
    We created one other reference when the link was created.
    10891089
    The linked list only has borrowed references. */
    1090-
    popresult = _PyDict_Pop_KnownHash(self->cache, link->key,
    1091-
    link->hash, Py_None);
    1092-
    if (popresult == Py_None) {
    1093-
    /* Getting here means that the user function call or another
    1094-
    thread has already removed the old key from the dictionary.
    1095-
    This link is now an orphan. Since we don't want to leave the
    1096-
    cache in an inconsistent state, we don't restore the link. */
    1097-
    Py_DECREF(popresult);
    1098-
    Py_DECREF(link);
    1099-
    Py_DECREF(key);
    1100-
    return result;
    1101-
    }
    1102-
    if (popresult == NULL) {
    1090+
    int res = _PyDict_Pop_KnownHash((PyDictObject*)self->cache, link->key,
    1091+
    link->hash, &popresult);
    1092+
    if (res < 0) {
    11031093
    /* An error arose while trying to remove the oldest key (the one
    11041094
    being evicted) from the cache. We restore the link to its
    11051095
    original position as the oldest link. Then we allow the
    @@ -1110,10 +1100,22 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
    11101100
    Py_DECREF(result);
    11111101
    return NULL;
    11121102
    }
    1103+
    if (res == 0) {
    1104+
    /* Getting here means that the user function call or another
    1105+
    thread has already removed the old key from the dictionary.
    1106+
    This link is now an orphan. Since we don't want to leave the
    1107+
    cache in an inconsistent state, we don't restore the link. */
    1108+
    assert(popresult == NULL);
    1109+
    Py_DECREF(link);
    1110+
    Py_DECREF(key);
    1111+
    return result;
    1112+
    }
    1113+
    11131114
    /* Keep a reference to the old key and old result to prevent their
    11141115
    ref counts from going to zero during the update. That will
    11151116
    prevent potentially arbitrary object clean-up code (i.e. __del__)
    11161117
    from running while we're still adjusting the links. */
    1118+
    assert(popresult != NULL);
    11171119
    oldkey = link->key;
    11181120
    oldresult = link->result;
    11191121

    0 commit comments

    Comments
     (0)
    0