Skip to content

Commit 4bf4371

Browse files
gh-106307: C API: Add PyMapping_GetOptionalItem() function (GH-106308)
Also add PyMapping_GetOptionalItemString() function.
1 parent b444bfb commit 4bf4371

File tree

15 files changed

+739
-896
lines changed

15 files changed

+739
-896
lines changed

Doc/c-api/mapping.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,36 @@ See also :c:func:`PyObject_GetItem`, :c:func:`PyObject_SetItem` and
3333
See also :c:func:`PyObject_GetItem`.
3434
3535
36+
.. c:function:: int PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)
37+
38+
Variant of :c:func:`PyObject_GetItem` which doesn't raise
39+
:exc:`KeyError` if the key is not found.
40+
41+
If the key is found, return ``1`` and set *\*result* to a new
42+
:term:`strong reference` to the corresponding value.
43+
If the key is not found, return ``0`` and set *\*result* to ``NULL``;
44+
the :exc:`KeyError` is silenced.
45+
If an error other than :exc:`KeyError` is raised, return ``-1`` and
46+
set *\*result* to ``NULL``.
47+
48+
.. versionadded:: 3.13
49+
50+
51+
.. c:function:: int PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result)
52+
53+
Variant of :c:func:`PyMapping_GetItemString` which doesn't raise
54+
:exc:`KeyError` if the key is not found.
55+
56+
If the key is found, return ``1`` and set *\*result* to a new
57+
:term:`strong reference` to the corresponding value.
58+
If the key is not found, return ``0`` and set *\*result* to ``NULL``;
59+
the :exc:`KeyError` is silenced.
60+
If an error other than :exc:`KeyError` is raised, return ``-1`` and
61+
set *\*result* to ``NULL``.
62+
63+
.. versionadded:: 3.13
64+
65+
3666
.. c:function:: int PyMapping_SetItemString(PyObject *o, const char *key, PyObject *v)
3767
3868
Map the string *key* to the value *v* in object *o*. Returns ``-1`` on

Doc/data/stable_abi.dat

Lines changed: 2 additions & 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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,14 @@ New Features
742742
should not be treated as a failure.
743743
(Contributed by Serhiy Storchaka in :gh:`106521`.)
744744

745+
* Add :c:func:`PyMapping_GetOptionalItem` and
746+
:c:func:`PyMapping_GetOptionalItemString`: variants of
747+
:c:func:`PyObject_GetItem` and :c:func:`PyMapping_GetItemString` which don't
748+
raise :exc:`KeyError` if the key is not found.
749+
These variants are more convenient and faster if the missing key should not
750+
be treated as a failure.
751+
(Contributed by Serhiy Storchaka in :gh:`106307`.)
752+
745753
* If Python is built in :ref:`debug mode <debug-build>` or :option:`with
746754
assertions <--with-assertions>`, :c:func:`PyTuple_SET_ITEM` and
747755
:c:func:`PyList_SET_ITEM` now check the index argument with an assertion.

Include/abstract.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,21 @@ PyAPI_FUNC(PyObject *) PyMapping_Items(PyObject *o);
840840
PyAPI_FUNC(PyObject *) PyMapping_GetItemString(PyObject *o,
841841
const char *key);
842842

843+
/* Variants of PyObject_GetItem() and PyMapping_GetItemString() which don't
844+
raise KeyError if the key is not found.
845+
846+
If the key is found, return 1 and set *result to a new strong
847+
reference to the corresponding value.
848+
If the key is not found, return 0 and set *result to NULL;
849+
the KeyError is silenced.
850+
If an error other than KeyError is raised, return -1 and
851+
set *result to NULL.
852+
*/
853+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
854+
PyAPI_FUNC(int) PyMapping_GetOptionalItem(PyObject *, PyObject *, PyObject **);
855+
PyAPI_FUNC(int) PyMapping_GetOptionalItemString(PyObject *, const char *, PyObject **);
856+
#endif
857+
843858
/* Map the string 'key' to the value 'v' in the mapping 'o'.
844859
Returns -1 on failure.
845860

Lib/test/test_stable_abi_ctypes.py

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add :c:func:`PyMapping_GetOptionalItem` function.

Misc/stable_abi.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2440,3 +2440,7 @@
24402440
added = '3.13'
24412441
[function.PyObject_GetOptionalAttrString]
24422442
added = '3.13'
2443+
[function.PyMapping_GetOptionalItem]
2444+
added = '3.13'
2445+
[function.PyMapping_GetOptionalItemString]
2446+
added = '3.13'

Modules/_lzmamodule.c

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,10 @@ parse_filter_spec_lzma(_lzma_state *state, PyObject *spec)
240240
/* First, fill in default values for all the options using a preset.
241241
Then, override the defaults with any values given by the caller. */
242242

243-
preset_obj = PyMapping_GetItemString(spec, "preset");
244-
if (preset_obj == NULL) {
245-
if (PyErr_ExceptionMatches(PyExc_KeyError)) {
246-
PyErr_Clear();
247-
}
248-
else {
249-
return NULL;
250-
}
251-
} else {
243+
if (PyMapping_GetOptionalItemString(spec, "preset", &preset_obj) < 0) {
244+
return NULL;
245+
}
246+
if (preset_obj != NULL) {
252247
int ok = uint32_converter(preset_obj, &preset);
253248
Py_DECREF(preset_obj);
254249
if (!ok) {
@@ -345,11 +340,12 @@ lzma_filter_converter(_lzma_state *state, PyObject *spec, void *ptr)
345340
"Filter specifier must be a dict or dict-like object");
346341
return 0;
347342
}
348-
id_obj = PyMapping_GetItemString(spec, "id");
343+
if (PyMapping_GetOptionalItemString(spec, "id", &id_obj) < 0) {
344+
return 0;
345+
}
349346
if (id_obj == NULL) {
350-
if (PyErr_ExceptionMatches(PyExc_KeyError))
351-
PyErr_SetString(PyExc_ValueError,
352-
"Filter specifier must have an \"id\" entry");
347+
PyErr_SetString(PyExc_ValueError,
348+
"Filter specifier must have an \"id\" entry");
353349
return 0;
354350
}
355351
f->id = PyLong_AsUnsignedLongLong(id_obj);

Modules/_pickle.c

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4438,16 +4438,13 @@ save(PickleState *st, PicklerObject *self, PyObject *obj, int pers_save)
44384438
PyObject_GetItem and _PyObject_GetAttrId used below. */
44394439
Py_INCREF(reduce_func);
44404440
}
4441-
} else {
4442-
reduce_func = PyObject_GetItem(self->dispatch_table,
4443-
(PyObject *)type);
4444-
if (reduce_func == NULL) {
4445-
if (PyErr_ExceptionMatches(PyExc_KeyError))
4446-
PyErr_Clear();
4447-
else
4448-
goto error;
4449-
}
44504441
}
4442+
else if (PyMapping_GetOptionalItem(self->dispatch_table, (PyObject *)type,
4443+
&reduce_func) < 0)
4444+
{
4445+
goto error;
4446+
}
4447+
44514448
if (reduce_func != NULL) {
44524449
reduce_value = _Pickle_FastCall(reduce_func, Py_NewRef(obj));
44534450
}

Objects/abstract.c

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,30 @@ PyObject_GetItem(PyObject *o, PyObject *key)
199199
return type_error("'%.200s' object is not subscriptable", o);
200200
}
201201

202+
int
203+
PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)
204+
{
205+
if (PyDict_CheckExact(obj)) {
206+
*result = PyDict_GetItemWithError(obj, key); /* borrowed */
207+
if (*result) {
208+
Py_INCREF(*result);
209+
return 1;
210+
}
211+
return PyErr_Occurred() ? -1 : 0;
212+
}
213+
214+
*result = PyObject_GetItem(obj, key);
215+
if (*result) {
216+
return 1;
217+
}
218+
assert(PyErr_Occurred());
219+
if (!PyErr_ExceptionMatches(PyExc_KeyError)) {
220+
return -1;
221+
}
222+
PyErr_Clear();
223+
return 0;
224+
}
225+
202226
int
203227
PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value)
204228
{
@@ -2366,6 +2390,22 @@ PyMapping_GetItemString(PyObject *o, const char *key)
23662390
return r;
23672391
}
23682392

2393+
int
2394+
PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result)
2395+
{
2396+
if (key == NULL) {
2397+
null_error();
2398+
return -1;
2399+
}
2400+
PyObject *okey = PyUnicode_FromString(key);
2401+
if (okey == NULL) {
2402+
return -1;
2403+
}
2404+
int rc = PyMapping_GetOptionalItem(obj, okey, result);
2405+
Py_DECREF(okey);
2406+
return rc;
2407+
}
2408+
23692409
int
23702410
PyMapping_SetItemString(PyObject *o, const char *key, PyObject *value)
23712411
{

0 commit comments

Comments
 (0)