Skip to content

Commit

Permalink
pythongh-106521: Add PyObject_GetOptionalAttr() function
Browse files Browse the repository at this point in the history
It is a new name of former _PyObject_LookupAttr().
  • Loading branch information
serhiy-storchaka committed Jul 7, 2023
1 parent 363f4f9 commit cf4d3cd
Show file tree
Hide file tree
Showing 50 changed files with 395 additions and 376 deletions.
19 changes: 18 additions & 1 deletion Doc/c-api/object.rst
Expand Up @@ -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)
Expand All @@ -60,6 +61,22 @@ 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:: int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result);
Replacement of :c:func:`PyObject_GetAttr` which doesn't raise
:exc:`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 :exc:`AttributeError` is silenced.
Return ``-1`` and set ``*result == NULL`` if an error other than
:exc:`AttributeError` is raised.
.. versionadded:: 3.13
.. c:function:: PyObject* PyObject_GetAttrString(PyObject *o, const char *attr_name)
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.13.rst
Expand Up @@ -441,6 +441,12 @@ New Features
``NULL`` if the referent is no longer live.
(Contributed by Victor Stinner in :gh:`105927`.)

* Add :c:func:`PyObject_GetOptionalAttr` function: replacement of
:c:func:`PyObject_GetAttr` which doesn't raise :exc:`AttributeError`.
It is 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
14 changes: 14 additions & 0 deletions Include/abstract.h
Expand Up @@ -60,6 +60,20 @@ 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);
Replacement of PyObject_GetAttr() which doesn'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.
*/


/* Implemented elsewhere:
int PyObject_SetAttrString(PyObject *o, const char *attr_name, PyObject *v);
Expand Down
11 changes: 0 additions & 11 deletions Include/cpython/object.h
Expand Up @@ -293,17 +293,6 @@ 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 **);

PyAPI_FUNC(int) _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method);

Expand Down
3 changes: 3 additions & 0 deletions Include/object.h
Expand Up @@ -393,6 +393,9 @@ PyAPI_FUNC(PyObject *) PyObject_GetAttrString(PyObject *, const char *);
PyAPI_FUNC(int) PyObject_SetAttrString(PyObject *, const char *, PyObject *);
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 **);
#endif
PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *);
PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *);
PyAPI_FUNC(PyObject *) PyObject_SelfIter(PyObject *);
Expand Down
@@ -0,0 +1 @@
Add :c:func:`PyObject_GetOptionalAttr` function.
4 changes: 2 additions & 2 deletions Modules/_abc.c
Expand Up @@ -361,7 +361,7 @@ compute_abstract_methods(PyObject *self)
PyObject *item = PyTuple_GET_ITEM(bases, pos); // borrowed
PyObject *base_abstracts, *iter;

if (_PyObject_LookupAttr(item, &_Py_ID(__abstractmethods__),
if (PyObject_GetOptionalAttr(item, &_Py_ID(__abstractmethods__),
&base_abstracts) < 0) {
goto error;
}
Expand All @@ -375,7 +375,7 @@ compute_abstract_methods(PyObject *self)
Py_DECREF(base_abstracts);
PyObject *key, *value;
while ((key = PyIter_Next(iter))) {
if (_PyObject_LookupAttr(self, key, &value) < 0) {
if (PyObject_GetOptionalAttr(self, key, &value) < 0) {
Py_DECREF(key);
Py_DECREF(iter);
goto error;
Expand Down
4 changes: 2 additions & 2 deletions Modules/_asynciomodule.c
Expand Up @@ -249,7 +249,7 @@ get_future_loop(asyncio_state *state, PyObject *fut)
return Py_NewRef(loop);
}

if (_PyObject_LookupAttr(fut, &_Py_ID(get_loop), &getloop) < 0) {
if (PyObject_GetOptionalAttr(fut, &_Py_ID(get_loop), &getloop) < 0) {
return NULL;
}
if (getloop != NULL) {
Expand Down Expand Up @@ -2981,7 +2981,7 @@ task_step_handle_result_impl(asyncio_state *state, TaskObj *task, PyObject *resu
}

/* Check if `result` is a Future-compatible object */
if (_PyObject_LookupAttr(result, &_Py_ID(_asyncio_future_blocking), &o) < 0) {
if (PyObject_GetOptionalAttr(result, &_Py_ID(_asyncio_future_blocking), &o) < 0) {
goto fail;
}
if (o != NULL && o != Py_None) {
Expand Down
2 changes: 1 addition & 1 deletion Modules/_csv.c
Expand Up @@ -1450,7 +1450,7 @@ csv_writer(PyObject *module, PyObject *args, PyObject *keyword_args)
Py_DECREF(self);
return NULL;
}
if (_PyObject_LookupAttr(output_file,
if (PyObject_GetOptionalAttr(output_file,
module_state->str_write,
&self->write) < 0) {
Py_DECREF(self);
Expand Down
22 changes: 11 additions & 11 deletions Modules/_ctypes/_ctypes.c
Expand Up @@ -851,7 +851,7 @@ CDataType_from_param(PyObject *type, PyObject *value)
return NULL;
}

if (_PyObject_LookupAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
if (PyObject_GetOptionalAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
return NULL;
}
if (as_parameter) {
Expand Down Expand Up @@ -1495,7 +1495,7 @@ PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
stgdict = NULL;
type_attr = NULL;

if (_PyObject_LookupAttr((PyObject *)result, &_Py_ID(_length_), &length_attr) < 0) {
if (PyObject_GetOptionalAttr((PyObject *)result, &_Py_ID(_length_), &length_attr) < 0) {
goto error;
}
if (!length_attr) {
Expand Down Expand Up @@ -1528,7 +1528,7 @@ PyCArrayType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
goto error;
}

if (_PyObject_LookupAttr((PyObject *)result, &_Py_ID(_type_), &type_attr) < 0) {
if (PyObject_GetOptionalAttr((PyObject *)result, &_Py_ID(_type_), &type_attr) < 0) {
goto error;
}
if (!type_attr) {
Expand Down Expand Up @@ -1720,7 +1720,7 @@ c_wchar_p_from_param(PyObject *type, PyObject *value)
}
}

if (_PyObject_LookupAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
if (PyObject_GetOptionalAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
return NULL;
}
if (as_parameter) {
Expand Down Expand Up @@ -1784,7 +1784,7 @@ c_char_p_from_param(PyObject *type, PyObject *value)
}
}

if (_PyObject_LookupAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
if (PyObject_GetOptionalAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
return NULL;
}
if (as_parameter) {
Expand Down Expand Up @@ -1919,7 +1919,7 @@ c_void_p_from_param(PyObject *type, PyObject *value)
}
}

if (_PyObject_LookupAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
if (PyObject_GetOptionalAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
return NULL;
}
if (as_parameter) {
Expand Down Expand Up @@ -2054,7 +2054,7 @@ PyCSimpleType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
if (result == NULL)
return NULL;

if (_PyObject_LookupAttr((PyObject *)result, &_Py_ID(_type_), &proto) < 0) {
if (PyObject_GetOptionalAttr((PyObject *)result, &_Py_ID(_type_), &proto) < 0) {
return NULL;
}
if (!proto) {
Expand Down Expand Up @@ -2266,7 +2266,7 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value)
PyObject *exc = PyErr_GetRaisedException();
Py_DECREF(parg);

if (_PyObject_LookupAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
if (PyObject_GetOptionalAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
Py_XDECREF(exc);
return NULL;
}
Expand Down Expand Up @@ -2429,7 +2429,7 @@ converters_from_argtypes(PyObject *ob)
}
*/

if (_PyObject_LookupAttr(tp, &_Py_ID(from_param), &cnv) <= 0) {
if (PyObject_GetOptionalAttr(tp, &_Py_ID(from_param), &cnv) <= 0) {
Py_DECREF(converters);
Py_DECREF(ob);
if (!PyErr_Occurred()) {
Expand Down Expand Up @@ -2489,7 +2489,7 @@ make_funcptrtype_dict(StgDictObject *stgdict)
return -1;
}
stgdict->restype = Py_NewRef(ob);
if (_PyObject_LookupAttr(ob, &_Py_ID(_check_retval_),
if (PyObject_GetOptionalAttr(ob, &_Py_ID(_check_retval_),
&stgdict->checker) < 0)
{
return -1;
Expand Down Expand Up @@ -3275,7 +3275,7 @@ PyCFuncPtr_set_restype(PyCFuncPtrObject *self, PyObject *ob, void *Py_UNUSED(ign
"restype must be a type, a callable, or None");
return -1;
}
if (_PyObject_LookupAttr(ob, &_Py_ID(_check_retval_), &checker) < 0) {
if (PyObject_GetOptionalAttr(ob, &_Py_ID(_check_retval_), &checker) < 0) {
return -1;
}
oldchecker = self->checker;
Expand Down
2 changes: 1 addition & 1 deletion Modules/_ctypes/callproc.c
Expand Up @@ -727,7 +727,7 @@ static int ConvParam(PyObject *obj, Py_ssize_t index, struct argument *pa)

{
PyObject *arg;
if (_PyObject_LookupAttr(obj, &_Py_ID(_as_parameter_), &arg) < 0) {
if (PyObject_GetOptionalAttr(obj, &_Py_ID(_as_parameter_), &arg) < 0) {
return -1;
}
/* Which types should we exactly allow here?
Expand Down
6 changes: 3 additions & 3 deletions Modules/_ctypes/stgdict.c
Expand Up @@ -295,7 +295,7 @@ MakeAnonFields(PyObject *type)
PyObject *anon_names;
Py_ssize_t i;

if (_PyObject_LookupAttr(type, &_Py_ID(_anonymous_), &anon) < 0) {
if (PyObject_GetOptionalAttr(type, &_Py_ID(_anonymous_), &anon) < 0) {
return -1;
}
if (anon == NULL) {
Expand Down Expand Up @@ -385,7 +385,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
if (fields == NULL)
return 0;

if (_PyObject_LookupAttr(type, &_Py_ID(_swappedbytes_), &tmp) < 0) {
if (PyObject_GetOptionalAttr(type, &_Py_ID(_swappedbytes_), &tmp) < 0) {
return -1;
}
if (tmp) {
Expand All @@ -396,7 +396,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
big_endian = PY_BIG_ENDIAN;
}

if (_PyObject_LookupAttr(type, &_Py_ID(_pack_), &tmp) < 0) {
if (PyObject_GetOptionalAttr(type, &_Py_ID(_pack_), &tmp) < 0) {
return -1;
}
if (tmp) {
Expand Down
2 changes: 1 addition & 1 deletion Modules/_datetimemodule.c
Expand Up @@ -3791,7 +3791,7 @@ tzinfo_reduce(PyObject *self, PyObject *Py_UNUSED(ignored))
PyObject *args, *state;
PyObject *getinitargs;

if (_PyObject_LookupAttr(self, &_Py_ID(__getinitargs__), &getinitargs) < 0) {
if (PyObject_GetOptionalAttr(self, &_Py_ID(__getinitargs__), &getinitargs) < 0) {
return NULL;
}
if (getinitargs != NULL) {
Expand Down
2 changes: 1 addition & 1 deletion Modules/_elementtree.c
Expand Up @@ -3530,7 +3530,7 @@ expat_start_doctype_handler(XMLParserObject *self,
sysid_obj, NULL);
Py_XDECREF(res);
}
else if (_PyObject_LookupAttr((PyObject *)self, st->str_doctype, &res) > 0) {
else if (PyObject_GetOptionalAttr((PyObject *)self, st->str_doctype, &res) > 0) {
(void)PyErr_WarnEx(PyExc_RuntimeWarning,
"The doctype() method of XMLParser is ignored. "
"Define doctype() method on the TreeBuilder target.",
Expand Down
4 changes: 2 additions & 2 deletions Modules/_io/bufferedio.c
Expand Up @@ -1463,7 +1463,7 @@ buffered_repr(buffered *self)
{
PyObject *nameobj, *res;

if (_PyObject_LookupAttr((PyObject *) self, &_Py_ID(name), &nameobj) < 0) {
if (PyObject_GetOptionalAttr((PyObject *) self, &_Py_ID(name), &nameobj) < 0) {
if (!PyErr_ExceptionMatches(PyExc_ValueError)) {
return NULL;
}
Expand Down Expand Up @@ -1630,7 +1630,7 @@ _bufferedreader_read_all(buffered *self)
}
_bufferedreader_reset_buf(self);

if (_PyObject_LookupAttr(self->raw, &_Py_ID(readall), &readall) < 0) {
if (PyObject_GetOptionalAttr(self->raw, &_Py_ID(readall), &readall) < 0) {
goto cleanup;
}
if (readall) {
Expand Down
2 changes: 1 addition & 1 deletion Modules/_io/fileio.c
Expand Up @@ -1099,7 +1099,7 @@ fileio_repr(fileio *self)
if (self->fd < 0)
return PyUnicode_FromFormat("<_io.FileIO [closed]>");

if (_PyObject_LookupAttr((PyObject *) self, &_Py_ID(name), &nameobj) < 0) {
if (PyObject_GetOptionalAttr((PyObject *) self, &_Py_ID(name), &nameobj) < 0) {
return NULL;
}
if (nameobj == NULL) {
Expand Down
8 changes: 4 additions & 4 deletions Modules/_io/iobase.c
Expand Up @@ -148,7 +148,7 @@ iobase_is_closed(PyObject *self)
int ret;
/* This gets the derived attribute, which is *not* __IOBase_closed
in most cases! */
ret = _PyObject_LookupAttr(self, &_Py_ID(__IOBase_closed), &res);
ret = PyObject_GetOptionalAttr(self, &_Py_ID(__IOBase_closed), &res);
Py_XDECREF(res);
return ret;
}
Expand Down Expand Up @@ -196,7 +196,7 @@ iobase_check_closed(PyObject *self)
int closed;
/* This gets the derived attribute, which is *not* __IOBase_closed
in most cases! */
closed = _PyObject_LookupAttr(self, &_Py_ID(closed), &res);
closed = PyObject_GetOptionalAttr(self, &_Py_ID(closed), &res);
if (closed > 0) {
closed = PyObject_IsTrue(res);
Py_DECREF(res);
Expand Down Expand Up @@ -303,7 +303,7 @@ iobase_finalize(PyObject *self)

/* If `closed` doesn't exist or can't be evaluated as bool, then the
object is probably in an unusable state, so ignore. */
if (_PyObject_LookupAttr(self, &_Py_ID(closed), &res) <= 0) {
if (PyObject_GetOptionalAttr(self, &_Py_ID(closed), &res) <= 0) {
PyErr_Clear();
closed = -1;
}
Expand Down Expand Up @@ -571,7 +571,7 @@ _io__IOBase_readline_impl(PyObject *self, Py_ssize_t limit)
PyObject *peek, *buffer, *result;
Py_ssize_t old_size = -1;

if (_PyObject_LookupAttr(self, &_Py_ID(peek), &peek) < 0) {
if (PyObject_GetOptionalAttr(self, &_Py_ID(peek), &peek) < 0) {
return NULL;
}

Expand Down

0 comments on commit cf4d3cd

Please sign in to comment.