diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 22e7458013fb3d..6fc5b2d14dd327 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -37,7 +37,8 @@ Object Protocol Exceptions that occur when this calls :meth:`~object.__getattr__` and :meth:`~object.__getattribute__` methods are silently ignored. - For proper error handling, use :c:func:`PyObject_GetAttr` instead. + For proper error handling, use :c:func:`PyObject_GetOptionalAttr` or + :c:func:`PyObject_GetAttr` instead. .. c:function:: int PyObject_HasAttrString(PyObject *o, const char *attr_name) @@ -51,7 +52,8 @@ Object Protocol Exceptions that occur when this calls :meth:`~object.__getattr__` and :meth:`~object.__getattribute__` methods or while creating the temporary :class:`str` object are silently ignored. - For proper error handling, use :c:func:`PyObject_GetAttrString` instead. + For proper error handling, use :c:func:`PyObject_GetOptionalAttrString` + or :c:func:`PyObject_GetAttrString` instead. .. c:function:: PyObject* PyObject_GetAttr(PyObject *o, PyObject *attr_name) @@ -60,6 +62,9 @@ Object Protocol value on success, or ``NULL`` on failure. This is the equivalent of the Python expression ``o.attr_name``. + If the missing attribute should not be treated as a failure, you can use + :c:func:`PyObject_GetOptionalAttr` instead. + .. c:function:: PyObject* PyObject_GetAttrString(PyObject *o, const char *attr_name) @@ -67,6 +72,38 @@ Object Protocol value on success, or ``NULL`` on failure. This is the equivalent of the Python expression ``o.attr_name``. + If the missing attribute should not be treated as a failure, you can use + :c:func:`PyObject_GetOptionalAttrString` instead. + + +.. c:function:: int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result); + + Variant of :c:func:`PyObject_GetAttr` which doesn't raise + :exc:`AttributeError` if the attribute is not found. + + If the attribute is found, return ``1`` and set *\*result* to a new + :term:`strong reference` to the attribute. + If the attribute is not found, return ``0`` and set *\*result* to ``NULL``; + the :exc:`AttributeError` is silenced. + If an error other than :exc:`AttributeError` is raised, return ``-1`` and + set *\*result* to ``NULL``. + + .. versionadded:: 3.13 + + +.. c:function:: int PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result); + + Variant of :c:func:`PyObject_GetAttrString` which doesn't raise + :exc:`AttributeError` if the attribute is not found. + + If the attribute is found, return ``1`` and set *\*result* to a new + :term:`strong reference` to the attribute. + If the attribute is not found, return ``0`` and set *\*result* to ``NULL``; + the :exc:`AttributeError` is silenced. + If an error other than :exc:`AttributeError` is raised, return ``-1`` and + set *\*result* to ``NULL``. + + .. versionadded:: 3.13 .. c:function:: PyObject* PyObject_GenericGetAttr(PyObject *o, PyObject *name) diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat index 9a61ddc39a353f..d7e8b5b464a71b 100644 --- a/Doc/data/stable_abi.dat +++ b/Doc/data/stable_abi.dat @@ -512,6 +512,8 @@ function,PyObject_GetAttrString,3.2,, function,PyObject_GetBuffer,3.11,, function,PyObject_GetItem,3.2,, function,PyObject_GetIter,3.2,, +function,PyObject_GetOptionalAttr,3.13,, +function,PyObject_GetOptionalAttrString,3.13,, function,PyObject_GetTypeData,3.12,, function,PyObject_HasAttr,3.2,, function,PyObject_HasAttrString,3.2,, diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 8fc809deca139b..62981673140cf7 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -734,6 +734,14 @@ New Features ``NULL`` if the referent is no longer live. (Contributed by Victor Stinner in :gh:`105927`.) +* Add :c:func:`PyObject_GetOptionalAttr` and + :c:func:`PyObject_GetOptionalAttrString`, variants of + :c:func:`PyObject_GetAttr` and :c:func:`PyObject_GetAttrString` which + don't raise :exc:`AttributeError` if the attribute is not found. + These variants are more convenient and faster if the missing attribute + should not be treated as a failure. + (Contributed by Serhiy Storchaka in :gh:`106521`.) + * If Python is built in :ref:`debug mode ` 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. diff --git a/Include/abstract.h b/Include/abstract.h index c84d2c704e9605..f47ea1ae3210ea 100644 --- a/Include/abstract.h +++ b/Include/abstract.h @@ -60,6 +60,38 @@ extern "C" { This is the equivalent of the Python expression: o.attr_name. */ +/* Implemented elsewhere: + + int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result); + + Variant of PyObject_GetAttr() which doesn't raise AttributeError + if the attribute is not found. + + If the attribute is found, return 1 and set *result to a new strong + reference to the attribute. + If the attribute is not found, return 0 and set *result to NULL; + the AttributeError is silenced. + If an error other than AttributeError is raised, return -1 and + set *result to NULL. +*/ + + +/* Implemented elsewhere: + + int PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result); + + Variant of PyObject_GetAttrString() which doesn't raise AttributeError + if the attribute is not found. + + If the attribute is found, return 1 and set *result to a new strong + reference to the attribute. + If the attribute is not found, return 0 and set *result to NULL; + the AttributeError is silenced. + If an error other than AttributeError is raised, return -1 and + set *result to NULL. +*/ + + /* Implemented elsewhere: int PyObject_SetAttrString(PyObject *o, const char *attr_name, PyObject *v); diff --git a/Include/cpython/object.h b/Include/cpython/object.h index ef9006431bee2f..ba30c567c40ebd 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -294,17 +294,7 @@ PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *); PyAPI_FUNC(int) _PyObject_IsAbstract(PyObject *); PyAPI_FUNC(PyObject *) _PyObject_GetAttrId(PyObject *, _Py_Identifier *); PyAPI_FUNC(int) _PyObject_SetAttrId(PyObject *, _Py_Identifier *, PyObject *); -/* Replacements of PyObject_GetAttr() and _PyObject_GetAttrId() which - don't raise AttributeError. - - Return 1 and set *result != NULL if an attribute is found. - Return 0 and set *result == NULL if an attribute is not found; - an AttributeError is silenced. - Return -1 and set *result == NULL if an error other than AttributeError - is raised. -*/ -PyAPI_FUNC(int) _PyObject_LookupAttr(PyObject *, PyObject *, PyObject **); -PyAPI_FUNC(int) _PyObject_LookupAttrId(PyObject *, _Py_Identifier *, PyObject **); +#define _PyObject_LookupAttr PyObject_GetOptionalAttr PyAPI_FUNC(int) _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method); diff --git a/Include/object.h b/Include/object.h index dccab07e5f2c6f..7f2e4e90615e7b 100644 --- a/Include/object.h +++ b/Include/object.h @@ -394,6 +394,10 @@ PyAPI_FUNC(int) PyObject_SetAttrString(PyObject *, const char *, PyObject *); PyAPI_FUNC(int) PyObject_DelAttrString(PyObject *v, const char *name); PyAPI_FUNC(int) PyObject_HasAttrString(PyObject *, const char *); PyAPI_FUNC(PyObject *) PyObject_GetAttr(PyObject *, PyObject *); +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000 +PyAPI_FUNC(int) PyObject_GetOptionalAttr(PyObject *, PyObject *, PyObject **); +PyAPI_FUNC(int) PyObject_GetOptionalAttrString(PyObject *, const char *, PyObject **); +#endif PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_DelAttr(PyObject *v, PyObject *name); PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *); diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 20bc2624c81361..6aa3cf382f9c63 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -531,6 +531,8 @@ def test_windows_feature_macros(self): "PyObject_GetBuffer", "PyObject_GetItem", "PyObject_GetIter", + "PyObject_GetOptionalAttr", + "PyObject_GetOptionalAttrString", "PyObject_GetTypeData", "PyObject_HasAttr", "PyObject_HasAttrString", diff --git a/Misc/NEWS.d/next/C API/2023-07-07-19-14-00.gh-issue-106521.Veh9f3.rst b/Misc/NEWS.d/next/C API/2023-07-07-19-14-00.gh-issue-106521.Veh9f3.rst new file mode 100644 index 00000000000000..f38fd271e8a440 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-07-07-19-14-00.gh-issue-106521.Veh9f3.rst @@ -0,0 +1 @@ +Add :c:func:`PyObject_GetOptionalAttr` and :c:func:`PyObject_GetOptionalAttrString` functions. diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index c61fedf8390e28..1f6519cb1e1b26 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2436,3 +2436,7 @@ added = '3.13' [function.PyObject_DelAttrString] added = '3.13' +[function.PyObject_GetOptionalAttr] + added = '3.13' +[function.PyObject_GetOptionalAttrString] + added = '3.13' diff --git a/Objects/object.c b/Objects/object.c index 540ba5d07427cd..d30e048335ab63 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -692,7 +692,7 @@ _PyObject_FunctionStr(PyObject *x) { assert(!PyErr_Occurred()); PyObject *qualname; - int ret = _PyObject_LookupAttr(x, &_Py_ID(__qualname__), &qualname); + int ret = PyObject_GetOptionalAttr(x, &_Py_ID(__qualname__), &qualname); if (qualname == NULL) { if (ret < 0) { return NULL; @@ -701,7 +701,7 @@ _PyObject_FunctionStr(PyObject *x) } PyObject *module; PyObject *result = NULL; - ret = _PyObject_LookupAttr(x, &_Py_ID(__module__), &module); + ret = PyObject_GetOptionalAttr(x, &_Py_ID(__module__), &module); if (module != NULL && module != Py_None) { ret = PyObject_RichCompareBool(module, &_Py_ID(builtins), Py_NE); if (ret < 0) { @@ -957,7 +957,7 @@ _PyObject_IsAbstract(PyObject *obj) if (obj == NULL) return 0; - res = _PyObject_LookupAttr(obj, &_Py_ID(__isabstractmethod__), &isabstract); + res = PyObject_GetOptionalAttr(obj, &_Py_ID(__isabstractmethod__), &isabstract); if (res > 0) { res = PyObject_IsTrue(isabstract); Py_DECREF(isabstract); @@ -1049,7 +1049,7 @@ PyObject_GetAttr(PyObject *v, PyObject *name) } int -_PyObject_LookupAttr(PyObject *v, PyObject *name, PyObject **result) +PyObject_GetOptionalAttr(PyObject *v, PyObject *name, PyObject **result) { PyTypeObject *tp = Py_TYPE(v); @@ -1117,21 +1117,35 @@ _PyObject_LookupAttr(PyObject *v, PyObject *name, PyObject **result) } int -_PyObject_LookupAttrId(PyObject *v, _Py_Identifier *name, PyObject **result) +PyObject_GetOptionalAttrString(PyObject *obj, const char *name, PyObject **result) { - PyObject *oname = _PyUnicode_FromId(name); /* borrowed */ - if (!oname) { - *result = NULL; + if (Py_TYPE(obj)->tp_getattr == NULL) { + PyObject *oname = PyUnicode_FromString(name); + if (oname == NULL) { + *result = NULL; + return -1; + } + int rc = PyObject_GetOptionalAttr(obj, oname, result); + Py_DECREF(oname); + return rc; + } + + *result = (*Py_TYPE(obj)->tp_getattr)(obj, (char*)name); + if (*result != NULL) { + return 1; + } + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { return -1; } - return _PyObject_LookupAttr(v, oname, result); + PyErr_Clear(); + return 0; } int PyObject_HasAttr(PyObject *v, PyObject *name) { PyObject *res; - if (_PyObject_LookupAttr(v, name, &res) < 0) { + if (PyObject_GetOptionalAttr(v, name, &res) < 0) { PyErr_Clear(); return 0; } diff --git a/PC/python3dll.c b/PC/python3dll.c index a7173911c7c1e8..7d7192958eeb98 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -469,6 +469,8 @@ EXPORT_FUNC(PyObject_GetAttrString) EXPORT_FUNC(PyObject_GetBuffer) EXPORT_FUNC(PyObject_GetItem) EXPORT_FUNC(PyObject_GetIter) +EXPORT_FUNC(PyObject_GetOptionalAttr) +EXPORT_FUNC(PyObject_GetOptionalAttrString) EXPORT_FUNC(PyObject_GetTypeData) EXPORT_FUNC(PyObject_HasAttr) EXPORT_FUNC(PyObject_HasAttrString)