-
-
Notifications
You must be signed in to change notification settings - Fork 30.7k
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
gh-111262: Add PyDict_Pop() function [without default value nor KeyError] #112028
Conversation
Simplified example where static int
sys_set_object(PyInterpreterState *interp, PyObject *key, PyObject *v)
{
PyObject *sd = interp->sysdict;
if (v == NULL) {
if (PyDict_Pop(sd, key, NULL) < 0) {
return -1;
}
// no need to care about KeyError or Py_DECREF()
return 0;
}
else {
return PyDict_SetItem(sd, key, v);
}
} Example which uses the removed value: PyObject *ob;
if (PyDict_Pop(kwargs, key, &ob) < 0) {
goto error;
}
if (ob != NULL) {
result->ob_item[i] = ob;
}
else {
// no need to care about KeyError or Py_DECREF()
result->ob_item[i] = Py_NewRef(self->ob_item[i]);
} |
The function is not added to the limited C API: @encukou asks to wait until Python will have a C API Working Group. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR includes yet one change. It allows to pass NULL as a result address. It complicates the implementation, but makes the common use case simpler.
} | ||
|
||
|
||
PyObject * | ||
_PyDict_Pop(PyObject *dict, PyObject *key, PyObject *default_value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to remove it in a separated PR: see #112026
return NULL; | ||
} | ||
if (result == NULL) { | ||
result = Py_NewRef(Py_None); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
result = Py_NewRef(Py_None); | |
return PyLong_FromLong(res); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer to return (0, None)
to make the tests written in Python closer to what the C API returns. In test_capi.test_dict, you can see that as (0, NULL)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be confused with actual None. For example dict.get()
return an actual none, but several corresponding C API functions return NULL.
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 _testcapi.MISSING
in future.
As requested, I added I added tests on PyDict_PopString() and I also addresssed @serhiy-storchaka's review. |
@serhiy-storchaka: I added more tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM overall, just wording fixes.
Doc/whatsnew/3.13.rst
Outdated
* 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 the | ||
similar to :meth:`dict.pop`, but without the default value and do not raise | ||
:exc:`KeyError` if the key missing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's usually clearer to write "not raising an exception" (at all) than naming an explicit exception and leaving it open whether other exceptions might be raised in the described case.
* 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 the | |
similar to :meth:`dict.pop`, but without the default value and do not raise | |
:exc:`KeyError` if the key missing. | |
* 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 | |
an exception if the key missing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I applied your suggestion to the 3 places where I wrote that doc, but I kept KeyError. IMO it's useful to be explicit about KeyError to understand what we are talking about.
Misc/NEWS.d/next/C API/2023-11-10-10-21-38.gh-issue-111262.2utB5m.rst
Outdated
Show resolved
Hide resolved
At the beginning, I had no idea. It was very blurry. What helped me to make my own opinion was to actually use the different proposed API and look at the updated code. I really like this API without the default value: I'm convinced by these examples. In short, I prefer this PR. |
_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>
_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>
_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>
_PyDict_Pop_KnownHash(): remove the default value and the return type becomes an int.
📚 Documentation preview 📚: https://cpython-previews--112028.org.readthedocs.build/