FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-111262: Add PyDict_Pop() function · vstinner/cpython@0693e77 · GitHub

Repository navigation

Commit 0693e77

Browse files
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 babb787 commit 0693e77

19 files changed

Lines changed: 198 additions & 67 deletions

File tree

‎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
@@ -1168,6 +1168,12 @@ New Features
11681168
Python ``list.extend()`` and ``list.clear()`` methods.
11691169
(Contributed by Victor Stinner in :gh:`111138`.)
11701170

1171+
* Add :c:func:`PyDict_Pop` function: remove a key from a dictionary and
1172+
optionally return the removed value. This is the similar to :meth:`dict.pop`,
1173+
but without the default value and do not raise :exc:`KeyError` if the key
1174+
missing.
1175+
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)
1176+
11711177

11721178
Porting to Python 3.13
11731179
----------------------

‎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)

Back | FazBrowse Home | New Git URL