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

15 files changed

+739
-896
lines changed

Doc/c-api/mapping.rst

+30
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

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.13.rst

+8
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

+15
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

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

Misc/stable_abi.toml

+4
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

+9-13
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

+6-9
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

+40
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
{

PC/python3dll.c

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/bytecodes.c

+24-92
Original file line numberDiff line numberDiff line change
@@ -1086,26 +1086,11 @@ dummy_func(
10861086
}
10871087

10881088
inst(LOAD_BUILD_CLASS, ( -- bc)) {
1089-
if (PyDict_CheckExact(BUILTINS())) {
1090-
bc = _PyDict_GetItemWithError(BUILTINS(),
1091-
&_Py_ID(__build_class__));
1092-
if (bc == NULL) {
1093-
if (!_PyErr_Occurred(tstate)) {
1094-
_PyErr_SetString(tstate, PyExc_NameError,
1095-
"__build_class__ not found");
1096-
}
1097-
ERROR_IF(true, error);
1098-
}
1099-
Py_INCREF(bc);
1100-
}
1101-
else {
1102-
bc = PyObject_GetItem(BUILTINS(), &_Py_ID(__build_class__));
1103-
if (bc == NULL) {
1104-
if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError))
1105-
_PyErr_SetString(tstate, PyExc_NameError,
1106-
"__build_class__ not found");
1107-
ERROR_IF(true, error);
1108-
}
1089+
ERROR_IF(PyMapping_GetOptionalItem(BUILTINS(), &_Py_ID(__build_class__), &bc) < 0, error);
1090+
if (bc == NULL) {
1091+
_PyErr_SetString(tstate, PyExc_NameError,
1092+
"__build_class__ not found");
1093+
ERROR_IF(true, error);
11091094
}
11101095
}
11111096

@@ -1280,25 +1265,9 @@ dummy_func(
12801265

12811266
op(_LOAD_FROM_DICT_OR_GLOBALS, (mod_or_class_dict -- v)) {
12821267
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
1283-
if (PyDict_CheckExact(mod_or_class_dict)) {
1284-
v = PyDict_GetItemWithError(mod_or_class_dict, name);
1285-
if (v != NULL) {
1286-
Py_INCREF(v);
1287-
}
1288-
else if (_PyErr_Occurred(tstate)) {
1289-
Py_DECREF(mod_or_class_dict);
1290-
goto error;
1291-
}
1292-
}
1293-
else {
1294-
v = PyObject_GetItem(mod_or_class_dict, name);
1295-
if (v == NULL) {
1296-
if (!_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
1297-
Py_DECREF(mod_or_class_dict);
1298-
goto error;
1299-
}
1300-
_PyErr_Clear(tstate);
1301-
}
1268+
if (PyMapping_GetOptionalItem(mod_or_class_dict, name, &v) < 0) {
1269+
Py_DECREF(mod_or_class_dict);
1270+
goto error;
13021271
}
13031272
Py_DECREF(mod_or_class_dict);
13041273
if (v == NULL) {
@@ -1310,28 +1279,14 @@ dummy_func(
13101279
goto error;
13111280
}
13121281
else {
1313-
if (PyDict_CheckExact(BUILTINS())) {
1314-
v = PyDict_GetItemWithError(BUILTINS(), name);
1315-
if (v == NULL) {
1316-
if (!_PyErr_Occurred(tstate)) {
1317-
format_exc_check_arg(
1318-
tstate, PyExc_NameError,
1319-
NAME_ERROR_MSG, name);
1320-
}
1321-
goto error;
1322-
}
1323-
Py_INCREF(v);
1282+
if (PyMapping_GetOptionalItem(BUILTINS(), name, &v) < 0) {
1283+
goto error;
13241284
}
1325-
else {
1326-
v = PyObject_GetItem(BUILTINS(), name);
1327-
if (v == NULL) {
1328-
if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
1329-
format_exc_check_arg(
1330-
tstate, PyExc_NameError,
1331-
NAME_ERROR_MSG, name);
1332-
}
1333-
goto error;
1334-
}
1285+
if (v == NULL) {
1286+
format_exc_check_arg(
1287+
tstate, PyExc_NameError,
1288+
NAME_ERROR_MSG, name);
1289+
goto error;
13351290
}
13361291
}
13371292
}
@@ -1381,19 +1336,14 @@ dummy_func(
13811336
/* Slow-path if globals or builtins is not a dict */
13821337

13831338
/* namespace 1: globals */
1384-
v = PyObject_GetItem(GLOBALS(), name);
1339+
ERROR_IF(PyMapping_GetOptionalItem(GLOBALS(), name, &v) < 0, error);
13851340
if (v == NULL) {
1386-
ERROR_IF(!_PyErr_ExceptionMatches(tstate, PyExc_KeyError), error);
1387-
_PyErr_Clear(tstate);
1388-
13891341
/* namespace 2: builtins */
1390-
v = PyObject_GetItem(BUILTINS(), name);
1342+
ERROR_IF(PyMapping_GetOptionalItem(BUILTINS(), name, &v) < 0, error);
13911343
if (v == NULL) {
1392-
if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
1393-
format_exc_check_arg(
1394-
tstate, PyExc_NameError,
1395-
NAME_ERROR_MSG, name);
1396-
}
1344+
format_exc_check_arg(
1345+
tstate, PyExc_NameError,
1346+
NAME_ERROR_MSG, name);
13971347
ERROR_IF(true, error);
13981348
}
13991349
}
@@ -1466,25 +1416,9 @@ dummy_func(
14661416
assert(class_dict);
14671417
assert(oparg >= 0 && oparg < _PyFrame_GetCode(frame)->co_nlocalsplus);
14681418
name = PyTuple_GET_ITEM(_PyFrame_GetCode(frame)->co_localsplusnames, oparg);
1469-
if (PyDict_CheckExact(class_dict)) {
1470-
value = PyDict_GetItemWithError(class_dict, name);
1471-
if (value != NULL) {
1472-
Py_INCREF(value);
1473-
}
1474-
else if (_PyErr_Occurred(tstate)) {
1475-
Py_DECREF(class_dict);
1476-
goto error;
1477-
}
1478-
}
1479-
else {
1480-
value = PyObject_GetItem(class_dict, name);
1481-
if (value == NULL) {
1482-
if (!_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
1483-
Py_DECREF(class_dict);
1484-
goto error;
1485-
}
1486-
_PyErr_Clear(tstate);
1487-
}
1419+
if (PyMapping_GetOptionalItem(class_dict, name, &value) < 0) {
1420+
Py_DECREF(class_dict);
1421+
goto error;
14881422
}
14891423
Py_DECREF(class_dict);
14901424
if (!value) {
@@ -1622,10 +1556,8 @@ dummy_func(
16221556
}
16231557
else {
16241558
/* do the same if locals() is not a dict */
1625-
ann_dict = PyObject_GetItem(LOCALS(), &_Py_ID(__annotations__));
1559+
ERROR_IF(PyMapping_GetOptionalItem(LOCALS(), &_Py_ID(__annotations__), &ann_dict) < 0, error);
16261560
if (ann_dict == NULL) {
1627-
ERROR_IF(!_PyErr_ExceptionMatches(tstate, PyExc_KeyError), error);
1628-
_PyErr_Clear(tstate);
16291561
ann_dict = PyDict_New();
16301562
ERROR_IF(ann_dict == NULL, error);
16311563
err = PyObject_SetItem(LOCALS(), &_Py_ID(__annotations__),

0 commit comments

Comments
 (0)