Skip to content
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

gh-106521: Add PyObject_GetOptionalAttr() function #106522

Merged
41 changes: 39 additions & 2 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
return it).


.. c:function:: int PyObject_Print(PyObject *o, FILE *fp, int flags)

Check warning on line 22 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

c:identifier reference target not found: FILE

Print an object *o*, on file *fp*. Returns ``-1`` on error. The flags argument

Check warning on line 24 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:const reference target not found: Py_PRINT_RAW
is used to enable certain printing options. The only option currently supported
is :const:`Py_PRINT_RAW`; if given, the :func:`str` of the object is written
instead of the :func:`repr`.
Expand All @@ -37,7 +37,8 @@

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)
Expand All @@ -51,7 +52,8 @@
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)
Expand All @@ -60,13 +62,48 @@
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)

Retrieve an attribute named *attr_name* from object *o*. Returns the attribute
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);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may just say:

Similar to :c:func:`PyObject_GetOptionalAttr`, but *name* is a UTF-8 encoded string.

To not copy/paste the documentation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In all other functions with String suffix the documentation is repeated. If we are going to change this, it should be changed for all function at once.

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)

Expand Down Expand Up @@ -161,7 +198,7 @@

.. c:function:: PyObject* PyObject_RichCompare(PyObject *o1, PyObject *o2, int opid)

Compare the values of *o1* and *o2* using the operation specified by *opid*,

Check warning on line 201 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:const reference target not found: Py_LT

Check warning on line 201 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:const reference target not found: Py_LE

Check warning on line 201 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:const reference target not found: Py_EQ

Check warning on line 201 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:const reference target not found: Py_NE

Check warning on line 201 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:const reference target not found: Py_GT

Check warning on line 201 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:const reference target not found: Py_GE
which must be one of :const:`Py_LT`, :const:`Py_LE`, :const:`Py_EQ`,
:const:`Py_NE`, :const:`Py_GT`, or :const:`Py_GE`, corresponding to ``<``,
``<=``, ``==``, ``!=``, ``>``, or ``>=`` respectively. This is the equivalent of
Expand All @@ -171,7 +208,7 @@

.. c:function:: int PyObject_RichCompareBool(PyObject *o1, PyObject *o2, int opid)

Compare the values of *o1* and *o2* using the operation specified by *opid*,

Check warning on line 211 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:const reference target not found: Py_LT

Check warning on line 211 in Doc/c-api/object.rst

View workflow job for this annotation

GitHub Actions / Docs / Docs

py:const reference target not found: Py_LE
which must be one of :const:`Py_LT`, :const:`Py_LE`, :const:`Py_EQ`,
:const:`Py_NE`, :const:`Py_GT`, or :const:`Py_GE`, corresponding to ``<``,
``<=``, ``==``, ``!=``, ``>``, or ``>=`` respectively. Returns ``-1`` on error,
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 @@ -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 <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
32 changes: 32 additions & 0 deletions Include/abstract.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 1 addition & 11 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 4 additions & 0 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
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:`PyObject_GetOptionalAttr` and :c:func:`PyObject_GetOptionalAttrString` functions.
4 changes: 4 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
34 changes: 24 additions & 10 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
}
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.