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
26 changes: 26 additions & 0 deletions Doc/c-api/mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,32 @@ 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`.

Return ``1`` and set ``*result != NULL`` if the key is found.
Return ``0`` and set ``*result == NULL`` if the key is not found;
the :exc:`KeyError` is silenced.
Return ``-1`` and set ``*result == NULL`` if an error other than
:exc:`KeyError` is raised.

.. 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`.

Return ``1`` and set ``*result != NULL`` if the key is found.
Return ``0`` and set ``*result == NULL`` if the key is not found;
the :exc:`KeyError` is silenced.
Return ``-1`` and set ``*result == NULL`` if an error other than
:exc:`KeyError` is raised.

.. 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 @@ -441,6 +441,14 @@ New Features
``NULL`` if the referent is no longer live.
(Contributed by Victor Stinner in :gh:`105927`.)

* Add :c:func:`PyMapping_GetOptionalItem` and
:c:func:`PyMapping_GetOptionalItemString`: variants of
:c:func:`PyObject_GetAttr` and :c:func:`PyMapping_GetItemString` which don't
raise :exc:`KeyError`.
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
14 changes: 14 additions & 0 deletions Include/abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,20 @@ 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.

Return 1 and set *result != NULL if the key is found.
Return 0 and set *result == NULL if the key is not found;
the KeyError is silenced.
Return -1 and set *result == NULL if an error other than KeyError
is raised.
*/
#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 @@ -977,6 +977,10 @@
added = '3.2'
[function.PyMapping_GetItemString]
added = '3.2'
[function.PyMapping_GetOptionalItem]
added = '3.13'
[function.PyMapping_GetOptionalItemString]
added = '3.13'
[function.PyMapping_HasKey]
added = '3.2'
[function.PyMapping_HasKeyString]
Expand Down
22 changes: 9 additions & 13 deletions Modules/_lzmamodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,15 +242,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 @@ -347,11 +342,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 @@ -4437,16 +4437,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