Skip to content

gh-106307: Add PyMapping_GetOptionalItem() #106308

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

Merged
merged 10 commits into from
Jul 11, 2023
30 changes: 30 additions & 0 deletions Doc/c-api/mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,36 @@ See also :c:func:`PyObject_GetItem`, :c:func:`PyObject_SetItem` and
See also :c:func:`PyObject_GetItem`.


.. c:function:: int PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)

Variant of :c:func:`PyObject_GetItem` which doesn't raise
:exc:`KeyError` if the key is not found.

If the key is found, return ``1`` and set *\*result* to a new
:term:`strong reference` to the corresponding value.
If the key is not found, return ``0`` and set *\*result* to ``NULL``;
the :exc:`KeyError` is silenced.
If an error other than :exc:`KeyError` is raised, return ``-1`` and
set *\*result* to ``NULL``.

.. versionadded:: 3.13


.. c:function:: int PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result)

Variant of :c:func:`PyMapping_GetItemString` which doesn't raise
:exc:`KeyError` if the key is not found.

If the key is found, return ``1`` and set *\*result* to a new
:term:`strong reference` to the corresponding value.
If the key is not found, return ``0`` and set *\*result* to ``NULL``;
the :exc:`KeyError` is silenced.
If an error other than :exc:`KeyError` is raised, return ``-1`` and
set *\*result* to ``NULL``.

.. versionadded:: 3.13


.. c:function:: int PyMapping_SetItemString(PyObject *o, const char *key, PyObject *v)

Map the string *key* to the value *v* in object *o*. Returns ``-1`` on
Expand Down
2 changes: 2 additions & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,14 @@ New Features
should not be treated as a failure.
(Contributed by Serhiy Storchaka in :gh:`106521`.)

* Add :c:func:`PyMapping_GetOptionalItem` and
:c:func:`PyMapping_GetOptionalItemString`: variants of
:c:func:`PyObject_GetItem` and :c:func:`PyMapping_GetItemString` which don't
raise :exc:`KeyError` if the key is not found.
These variants are more convenient and faster if the missing key should not
be treated as a failure.
(Contributed by Serhiy Storchaka in :gh:`106307`.)

* If Python is built in :ref:`debug mode <debug-build>` or :option:`with
assertions <--with-assertions>`, :c:func:`PyTuple_SET_ITEM` and
:c:func:`PyList_SET_ITEM` now check the index argument with an assertion.
Expand Down
15 changes: 15 additions & 0 deletions Include/abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,21 @@ PyAPI_FUNC(PyObject *) PyMapping_Items(PyObject *o);
PyAPI_FUNC(PyObject *) PyMapping_GetItemString(PyObject *o,
const char *key);

/* Variants of PyObject_GetItem() and PyMapping_GetItemString() which don't
raise KeyError if the key is not found.

If the key is found, return 1 and set *result to a new strong
reference to the corresponding value.
If the key is not found, return 0 and set *result to NULL;
the KeyError is silenced.
If an error other than KeyError is raised, return -1 and
set *result to NULL.
*/
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
PyAPI_FUNC(int) PyMapping_GetOptionalItem(PyObject *, PyObject *, PyObject **);
PyAPI_FUNC(int) PyMapping_GetOptionalItemString(PyObject *, const char *, PyObject **);
#endif

/* Map the string 'key' to the value 'v' in the mapping 'o'.
Returns -1 on failure.

Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add :c:func:`PyMapping_GetOptionalItem` function.
4 changes: 4 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2440,3 +2440,7 @@
added = '3.13'
[function.PyObject_GetOptionalAttrString]
added = '3.13'
[function.PyMapping_GetOptionalItem]
added = '3.13'
[function.PyMapping_GetOptionalItemString]
added = '3.13'
22 changes: 9 additions & 13 deletions Modules/_lzmamodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,10 @@ parse_filter_spec_lzma(_lzma_state *state, PyObject *spec)
/* First, fill in default values for all the options using a preset.
Then, override the defaults with any values given by the caller. */

preset_obj = PyMapping_GetItemString(spec, "preset");
if (preset_obj == NULL) {
if (PyErr_ExceptionMatches(PyExc_KeyError)) {
PyErr_Clear();
}
else {
return NULL;
}
} else {
if (PyMapping_GetOptionalItemString(spec, "preset", &preset_obj) < 0) {
return NULL;
}
if (preset_obj != NULL) {
int ok = uint32_converter(preset_obj, &preset);
Py_DECREF(preset_obj);
if (!ok) {
Expand Down Expand Up @@ -345,11 +340,12 @@ lzma_filter_converter(_lzma_state *state, PyObject *spec, void *ptr)
"Filter specifier must be a dict or dict-like object");
return 0;
}
id_obj = PyMapping_GetItemString(spec, "id");
if (PyMapping_GetOptionalItemString(spec, "id", &id_obj) < 0) {
return 0;
}
if (id_obj == NULL) {
if (PyErr_ExceptionMatches(PyExc_KeyError))
PyErr_SetString(PyExc_ValueError,
"Filter specifier must have an \"id\" entry");
PyErr_SetString(PyExc_ValueError,
"Filter specifier must have an \"id\" entry");
return 0;
}
f->id = PyLong_AsUnsignedLongLong(id_obj);
Expand Down
15 changes: 6 additions & 9 deletions Modules/_pickle.c
Original file line number Diff line number Diff line change
Expand Up @@ -4438,16 +4438,13 @@ save(PickleState *st, PicklerObject *self, PyObject *obj, int pers_save)
PyObject_GetItem and _PyObject_GetAttrId used below. */
Py_INCREF(reduce_func);
}
} else {
reduce_func = PyObject_GetItem(self->dispatch_table,
(PyObject *)type);
if (reduce_func == NULL) {
if (PyErr_ExceptionMatches(PyExc_KeyError))
PyErr_Clear();
else
goto error;
}
}
else if (PyMapping_GetOptionalItem(self->dispatch_table, (PyObject *)type,
&reduce_func) < 0)
{
goto error;
}

if (reduce_func != NULL) {
reduce_value = _Pickle_FastCall(reduce_func, Py_NewRef(obj));
}
Expand Down
40 changes: 40 additions & 0 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,30 @@ PyObject_GetItem(PyObject *o, PyObject *key)
return type_error("'%.200s' object is not subscriptable", o);
}

int
PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result)
{
if (PyDict_CheckExact(obj)) {
*result = PyDict_GetItemWithError(obj, key); /* borrowed */
if (*result) {
Py_INCREF(*result);
return 1;
}
return PyErr_Occurred() ? -1 : 0;
}

*result = PyObject_GetItem(obj, key);
if (*result) {
return 1;
}
assert(PyErr_Occurred());
if (!PyErr_ExceptionMatches(PyExc_KeyError)) {
return -1;
}
PyErr_Clear();
return 0;
}

int
PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value)
{
Expand Down Expand Up @@ -2366,6 +2390,22 @@ PyMapping_GetItemString(PyObject *o, const char *key)
return r;
}

int
PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result)
{
if (key == NULL) {
null_error();
return -1;
}
PyObject *okey = PyUnicode_FromString(key);
if (okey == NULL) {
return -1;
}
int rc = PyMapping_GetOptionalItem(obj, okey, result);
Py_DECREF(okey);
return rc;
}

int
PyMapping_SetItemString(PyObject *o, const char *key, PyObject *value)
{
Expand Down
2 changes: 2 additions & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

116 changes: 24 additions & 92 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1086,26 +1086,11 @@ dummy_func(
}

inst(LOAD_BUILD_CLASS, ( -- bc)) {
if (PyDict_CheckExact(BUILTINS())) {
bc = _PyDict_GetItemWithError(BUILTINS(),
&_Py_ID(__build_class__));
if (bc == NULL) {
if (!_PyErr_Occurred(tstate)) {
_PyErr_SetString(tstate, PyExc_NameError,
"__build_class__ not found");
}
ERROR_IF(true, error);
}
Py_INCREF(bc);
}
else {
bc = PyObject_GetItem(BUILTINS(), &_Py_ID(__build_class__));
if (bc == NULL) {
if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError))
_PyErr_SetString(tstate, PyExc_NameError,
"__build_class__ not found");
ERROR_IF(true, error);
}
ERROR_IF(PyMapping_GetOptionalItem(BUILTINS(), &_Py_ID(__build_class__), &bc) < 0, error);
if (bc == NULL) {
_PyErr_SetString(tstate, PyExc_NameError,
"__build_class__ not found");
ERROR_IF(true, error);
}
}

Expand Down Expand Up @@ -1280,25 +1265,9 @@ dummy_func(

op(_LOAD_FROM_DICT_OR_GLOBALS, (mod_or_class_dict -- v)) {
PyObject *name = GETITEM(FRAME_CO_NAMES, oparg);
if (PyDict_CheckExact(mod_or_class_dict)) {
v = PyDict_GetItemWithError(mod_or_class_dict, name);
if (v != NULL) {
Py_INCREF(v);
}
else if (_PyErr_Occurred(tstate)) {
Py_DECREF(mod_or_class_dict);
goto error;
}
}
else {
v = PyObject_GetItem(mod_or_class_dict, name);
if (v == NULL) {
if (!_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
Py_DECREF(mod_or_class_dict);
goto error;
}
_PyErr_Clear(tstate);
}
if (PyMapping_GetOptionalItem(mod_or_class_dict, name, &v) < 0) {
Py_DECREF(mod_or_class_dict);
goto error;
}
Py_DECREF(mod_or_class_dict);
if (v == NULL) {
Expand All @@ -1310,28 +1279,14 @@ dummy_func(
goto error;
}
else {
if (PyDict_CheckExact(BUILTINS())) {
v = PyDict_GetItemWithError(BUILTINS(), name);
if (v == NULL) {
if (!_PyErr_Occurred(tstate)) {
format_exc_check_arg(
tstate, PyExc_NameError,
NAME_ERROR_MSG, name);
}
goto error;
}
Py_INCREF(v);
if (PyMapping_GetOptionalItem(BUILTINS(), name, &v) < 0) {
goto error;
}
else {
v = PyObject_GetItem(BUILTINS(), name);
if (v == NULL) {
if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
format_exc_check_arg(
tstate, PyExc_NameError,
NAME_ERROR_MSG, name);
}
goto error;
}
if (v == NULL) {
format_exc_check_arg(
tstate, PyExc_NameError,
NAME_ERROR_MSG, name);
goto error;
}
}
}
Expand Down Expand Up @@ -1381,19 +1336,14 @@ dummy_func(
/* Slow-path if globals or builtins is not a dict */

/* namespace 1: globals */
v = PyObject_GetItem(GLOBALS(), name);
ERROR_IF(PyMapping_GetOptionalItem(GLOBALS(), name, &v) < 0, error);
if (v == NULL) {
ERROR_IF(!_PyErr_ExceptionMatches(tstate, PyExc_KeyError), error);
_PyErr_Clear(tstate);

/* namespace 2: builtins */
v = PyObject_GetItem(BUILTINS(), name);
ERROR_IF(PyMapping_GetOptionalItem(BUILTINS(), name, &v) < 0, error);
if (v == NULL) {
if (_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
format_exc_check_arg(
tstate, PyExc_NameError,
NAME_ERROR_MSG, name);
}
format_exc_check_arg(
tstate, PyExc_NameError,
NAME_ERROR_MSG, name);
ERROR_IF(true, error);
}
}
Expand Down Expand Up @@ -1466,25 +1416,9 @@ dummy_func(
assert(class_dict);
assert(oparg >= 0 && oparg < _PyFrame_GetCode(frame)->co_nlocalsplus);
name = PyTuple_GET_ITEM(_PyFrame_GetCode(frame)->co_localsplusnames, oparg);
if (PyDict_CheckExact(class_dict)) {
value = PyDict_GetItemWithError(class_dict, name);
if (value != NULL) {
Py_INCREF(value);
}
else if (_PyErr_Occurred(tstate)) {
Py_DECREF(class_dict);
goto error;
}
}
else {
value = PyObject_GetItem(class_dict, name);
if (value == NULL) {
if (!_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
Py_DECREF(class_dict);
goto error;
}
_PyErr_Clear(tstate);
}
if (PyMapping_GetOptionalItem(class_dict, name, &value) < 0) {
Py_DECREF(class_dict);
goto error;
}
Py_DECREF(class_dict);
if (!value) {
Expand Down Expand Up @@ -1622,10 +1556,8 @@ dummy_func(
}
else {
/* do the same if locals() is not a dict */
ann_dict = PyObject_GetItem(LOCALS(), &_Py_ID(__annotations__));
ERROR_IF(PyMapping_GetOptionalItem(LOCALS(), &_Py_ID(__annotations__), &ann_dict) < 0, error);
if (ann_dict == NULL) {
ERROR_IF(!_PyErr_ExceptionMatches(tstate, PyExc_KeyError), error);
_PyErr_Clear(tstate);
ann_dict = PyDict_New();
ERROR_IF(ann_dict == NULL, error);
err = PyObject_SetItem(LOCALS(), &_Py_ID(__annotations__),
Expand Down
Loading