Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-111262: Add PyDict_Pop() function [without default value nor KeyError]#112028
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
1498269ae73490f65c10f9da06c8343f75a2170fc0c3b8bd2a286604229f524File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -432,6 +432,93 @@ def test_dict_mergefromseq2(self): | ||
| # CRASHES mergefromseq2({}, NULL, 0) | ||
| # CRASHES mergefromseq2(NULL, {}, 0) | ||
| def test_dict_pop(self): | ||
| # Test PyDict_Pop() | ||
| dict_pop = _testcapi.dict_pop | ||
| dict_pop_null = _testcapi.dict_pop_null | ||
| # key present, get removed value | ||
| mydict = {"key": "value", "key2": "value2"} | ||
| self.assertEqual(dict_pop(mydict, "key"), (1, "value")) | ||
| self.assertEqual(mydict, {"key2": "value2"}) | ||
| self.assertEqual(dict_pop(mydict, "key2"), (1, "value2")) | ||
| self.assertEqual(mydict, {}) | ||
| # key present, ignore removed value | ||
| mydict = {"key": "value", "key2": "value2"} | ||
| self.assertEqual(dict_pop_null(mydict, "key"), 1) | ||
| self.assertEqual(mydict, {"key2": "value2"}) | ||
| self.assertEqual(dict_pop_null(mydict, "key2"), 1) | ||
| self.assertEqual(mydict, {}) | ||
| # key missing, expect removed value; empty dict has a fast path | ||
| self.assertEqual(dict_pop({}, "key"), (0, NULL)) | ||
| self.assertEqual(dict_pop({"a": 1}, "key"), (0, NULL)) | ||
| # key missing, ignored removed value; empty dict has a fast path | ||
| self.assertEqual(dict_pop_null({}, "key"), 0) | ||
| self.assertEqual(dict_pop_null({"a": 1}, "key"), 0) | ||
| # dict error | ||
| not_dict = UserDict({1: 2}) | ||
| self.assertRaises(SystemError, dict_pop, not_dict, "key") | ||
| self.assertRaises(SystemError, dict_pop_null, not_dict, "key") | ||
| # key error; don't hash key if dict is empty | ||
| not_hashable_key = ["list"] | ||
| self.assertEqual(dict_pop({}, not_hashable_key), (0, NULL)) | ||
| with self.assertRaises(TypeError): | ||
| dict_pop({'key': 1}, not_hashable_key) | ||
| dict_pop({}, NULL) # key is not checked if dict is empty | ||
| # CRASHES dict_pop(NULL, "key") | ||
| # CRASHES dict_pop({"a": 1}, NULL) | ||
| def test_dict_popstring(self): | ||
| # Test PyDict_PopString() | ||
| dict_popstring = _testcapi.dict_popstring | ||
| dict_popstring_null = _testcapi.dict_popstring_null | ||
| # key present, get removed value | ||
| mydict = {"key": "value", "key2": "value2"} | ||
| self.assertEqual(dict_popstring(mydict, "key"), (1, "value")) | ||
| self.assertEqual(mydict, {"key2": "value2"}) | ||
| self.assertEqual(dict_popstring(mydict, "key2"), (1, "value2")) | ||
| self.assertEqual(mydict, {}) | ||
| # key present, ignore removed value | ||
| mydict = {"key": "value", "key2": "value2"} | ||
| self.assertEqual(dict_popstring_null(mydict, "key"), 1) | ||
| self.assertEqual(mydict, {"key2": "value2"}) | ||
| self.assertEqual(dict_popstring_null(mydict, "key2"), 1) | ||
| self.assertEqual(mydict, {}) | ||
| # key missing; empty dict has a fast path | ||
| self.assertEqual(dict_popstring({}, "key"), (0, NULL)) | ||
| self.assertEqual(dict_popstring_null({}, "key"), 0) | ||
| self.assertEqual(dict_popstring({"a": 1}, "key"), (0, NULL)) | ||
| self.assertEqual(dict_popstring_null({"a": 1}, "key"), 0) | ||
| # non-ASCII key | ||
| non_ascii = '\U0001f40d' | ||
| dct = {'\U0001f40d': 123} | ||
| self.assertEqual(dict_popstring(dct, '\U0001f40d'.encode()), (1, 123)) | ||
| dct = {'\U0001f40d': 123} | ||
| self.assertEqual(dict_popstring_null(dct, '\U0001f40d'.encode()), 1) | ||
| # dict error | ||
| not_dict = UserDict({1: 2}) | ||
| self.assertRaises(SystemError, dict_popstring, not_dict, "key") | ||
vstinner marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| self.assertRaises(SystemError, dict_popstring_null, not_dict, "key") | ||
| # key error | ||
| self.assertRaises(UnicodeDecodeError, dict_popstring, {1: 2}, INVALID_UTF8) | ||
| self.assertRaises(UnicodeDecodeError, dict_popstring_null, {1: 2}, INVALID_UTF8) | ||
| # CRASHES dict_popstring(NULL, "key") | ||
| # CRASHES dict_popstring({}, NULL) | ||
| # CRASHES dict_popstring({"a": 1}, NULL) | ||
vstinner marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if __name__ == "__main__": | ||
| unittest.main() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Add :c:func:`PyDict_Pop` and :c:func:`PyDict_PopString` functions: remove a key | ||
| from a dictionary and optionally return the removed value. This is similar to | ||
| :meth:`dict.pop`, but without the default value and not raising :exc:`KeyError` | ||
| if the key missing. Patch by Stefan Behnel and Victor Stinner. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -331,6 +331,88 @@ dict_mergefromseq2(PyObject *self, PyObject *args) | ||||||
| } | ||||||
| static PyObject * | ||||||
| dict_pop(PyObject *self, PyObject *args) | ||||||
vstinner marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||
| { | ||||||
| // Test PyDict_Pop(dict, key, &value) | ||||||
| PyObject *dict, *key; | ||||||
| if (!PyArg_ParseTuple(args, "OO", &dict, &key)) { | ||||||
| return NULL; | ||||||
| } | ||||||
| NULLABLE(dict); | ||||||
| NULLABLE(key); | ||||||
| PyObject *result = UNINITIALIZED_PTR; | ||||||
| int res = PyDict_Pop(dict, key, &result); | ||||||
| if (res < 0) { | ||||||
| assert(result == NULL); | ||||||
| return NULL; | ||||||
vstinner marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||
| } | ||||||
| if (res == 0) { | ||||||
| assert(result == NULL); | ||||||
| result = Py_NewRef(Py_None); | ||||||
Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to return Member There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be confused with actual None. For example In other tests I made them returning AttributeError or KeyError, as it is less chance to confuse with real value, but I think that it would be better to use a special singleton | ||||||
| } | ||||||
| else { | ||||||
| assert(result != NULL); | ||||||
| } | ||||||
| return Py_BuildValue("iN", res, result); | ||||||
| } | ||||||
| static PyObject * | ||||||
| dict_pop_null(PyObject *self, PyObject *args) | ||||||
| { | ||||||
| // Test PyDict_Pop(dict, key, NULL) | ||||||
| PyObject *dict, *key; | ||||||
| if (!PyArg_ParseTuple(args, "OO", &dict, &key)) { | ||||||
| return NULL; | ||||||
| } | ||||||
| NULLABLE(dict); | ||||||
| NULLABLE(key); | ||||||
| RETURN_INT(PyDict_Pop(dict, key, NULL)); | ||||||
| } | ||||||
| static PyObject * | ||||||
| dict_popstring(PyObject *self, PyObject *args) | ||||||
vstinner marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||||||
| { | ||||||
| PyObject *dict; | ||||||
| const char *key; | ||||||
| Py_ssize_t key_size; | ||||||
| if (!PyArg_ParseTuple(args, "Oz#", &dict, &key, &key_size)) { | ||||||
| return NULL; | ||||||
| } | ||||||
| NULLABLE(dict); | ||||||
| PyObject *result = UNINITIALIZED_PTR; | ||||||
| int res = PyDict_PopString(dict, key, &result); | ||||||
| if (res < 0) { | ||||||
| assert(result == NULL); | ||||||
| return NULL; | ||||||
| } | ||||||
| if (res == 0) { | ||||||
| assert(result == NULL); | ||||||
| result = Py_NewRef(Py_None); | ||||||
| } | ||||||
| else { | ||||||
| assert(result != NULL); | ||||||
| } | ||||||
| return Py_BuildValue("iN", res, result); | ||||||
| } | ||||||
| static PyObject * | ||||||
| dict_popstring_null(PyObject *self, PyObject *args) | ||||||
| { | ||||||
| PyObject *dict; | ||||||
| const char *key; | ||||||
| Py_ssize_t key_size; | ||||||
| if (!PyArg_ParseTuple(args, "Oz#", &dict, &key, &key_size)) { | ||||||
| return NULL; | ||||||
| } | ||||||
| NULLABLE(dict); | ||||||
| RETURN_INT(PyDict_PopString(dict, key, NULL)); | ||||||
| } | ||||||
| static PyMethodDef test_methods[] = { | ||||||
| {"dict_check", dict_check, METH_O}, | ||||||
| {"dict_checkexact", dict_checkexact, METH_O}, | ||||||
| @@ -358,7 +440,10 @@ static PyMethodDef test_methods[] = { | ||||||
| {"dict_merge", dict_merge, METH_VARARGS}, | ||||||
| {"dict_update", dict_update, METH_VARARGS}, | ||||||
| {"dict_mergefromseq2", dict_mergefromseq2, METH_VARARGS}, | ||||||
| {"dict_pop", dict_pop, METH_VARARGS}, | ||||||
| {"dict_pop_null", dict_pop_null, METH_VARARGS}, | ||||||
| {"dict_popstring", dict_popstring, METH_VARARGS}, | ||||||
| {"dict_popstring_null", dict_popstring_null, METH_VARARGS}, | ||||||
| {NULL}, | ||||||
| }; | ||||||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.