Skip to content

Commit

Permalink
pythongh-106307: C API: Add PyMapping_GetOptionalItem() function (pyt…
Browse files Browse the repository at this point in the history
…honGH-106308)

Also add PyMapping_GetOptionalItemString() function.
  • Loading branch information
serhiy-storchaka authored Jul 11, 2023
1 parent b444bfb commit 4bf4371
Show file tree
Hide file tree
Showing 15 changed files with 739 additions and 896 deletions.
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

0 comments on commit 4bf4371

Please sign in to comment.