Skip to content

Commit 579aa89

Browse files
gh-106521: Add PyObject_GetOptionalAttr() function (GH-106522)
It is a new name of former _PyObject_LookupAttr(). Add also PyObject_GetOptionalAttrString().
1 parent cabd6e8 commit 579aa89

File tree

11 files changed

+119
-23
lines changed

11 files changed

+119
-23
lines changed

Doc/c-api/object.rst

+39-2
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ Object Protocol
3737
3838
Exceptions that occur when this calls :meth:`~object.__getattr__` and
3939
:meth:`~object.__getattribute__` methods are silently ignored.
40-
For proper error handling, use :c:func:`PyObject_GetAttr` instead.
40+
For proper error handling, use :c:func:`PyObject_GetOptionalAttr` or
41+
:c:func:`PyObject_GetAttr` instead.
4142
4243
4344
.. c:function:: int PyObject_HasAttrString(PyObject *o, const char *attr_name)
@@ -51,7 +52,8 @@ Object Protocol
5152
Exceptions that occur when this calls :meth:`~object.__getattr__` and
5253
:meth:`~object.__getattribute__` methods or while creating the temporary :class:`str`
5354
object are silently ignored.
54-
For proper error handling, use :c:func:`PyObject_GetAttrString` instead.
55+
For proper error handling, use :c:func:`PyObject_GetOptionalAttrString`
56+
or :c:func:`PyObject_GetAttrString` instead.
5557
5658
5759
.. c:function:: PyObject* PyObject_GetAttr(PyObject *o, PyObject *attr_name)
@@ -60,13 +62,48 @@ Object Protocol
6062
value on success, or ``NULL`` on failure. This is the equivalent of the Python
6163
expression ``o.attr_name``.
6264
65+
If the missing attribute should not be treated as a failure, you can use
66+
:c:func:`PyObject_GetOptionalAttr` instead.
67+
6368
6469
.. c:function:: PyObject* PyObject_GetAttrString(PyObject *o, const char *attr_name)
6570
6671
Retrieve an attribute named *attr_name* from object *o*. Returns the attribute
6772
value on success, or ``NULL`` on failure. This is the equivalent of the Python
6873
expression ``o.attr_name``.
6974
75+
If the missing attribute should not be treated as a failure, you can use
76+
:c:func:`PyObject_GetOptionalAttrString` instead.
77+
78+
79+
.. c:function:: int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result);
80+
81+
Variant of :c:func:`PyObject_GetAttr` which doesn't raise
82+
:exc:`AttributeError` if the attribute is not found.
83+
84+
If the attribute is found, return ``1`` and set *\*result* to a new
85+
:term:`strong reference` to the attribute.
86+
If the attribute is not found, return ``0`` and set *\*result* to ``NULL``;
87+
the :exc:`AttributeError` is silenced.
88+
If an error other than :exc:`AttributeError` is raised, return ``-1`` and
89+
set *\*result* to ``NULL``.
90+
91+
.. versionadded:: 3.13
92+
93+
94+
.. c:function:: int PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result);
95+
96+
Variant of :c:func:`PyObject_GetAttrString` which doesn't raise
97+
:exc:`AttributeError` if the attribute is not found.
98+
99+
If the attribute is found, return ``1`` and set *\*result* to a new
100+
:term:`strong reference` to the attribute.
101+
If the attribute is not found, return ``0`` and set *\*result* to ``NULL``;
102+
the :exc:`AttributeError` is silenced.
103+
If an error other than :exc:`AttributeError` is raised, return ``-1`` and
104+
set *\*result* to ``NULL``.
105+
106+
.. versionadded:: 3.13
70107
71108
.. c:function:: PyObject* PyObject_GenericGetAttr(PyObject *o, PyObject *name)
72109

Doc/data/stable_abi.dat

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.13.rst

+8
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,14 @@ New Features
734734
``NULL`` if the referent is no longer live.
735735
(Contributed by Victor Stinner in :gh:`105927`.)
736736

737+
* Add :c:func:`PyObject_GetOptionalAttr` and
738+
:c:func:`PyObject_GetOptionalAttrString`, variants of
739+
:c:func:`PyObject_GetAttr` and :c:func:`PyObject_GetAttrString` which
740+
don't raise :exc:`AttributeError` if the attribute is not found.
741+
These variants are more convenient and faster if the missing attribute
742+
should not be treated as a failure.
743+
(Contributed by Serhiy Storchaka in :gh:`106521`.)
744+
737745
* If Python is built in :ref:`debug mode <debug-build>` or :option:`with
738746
assertions <--with-assertions>`, :c:func:`PyTuple_SET_ITEM` and
739747
:c:func:`PyList_SET_ITEM` now check the index argument with an assertion.

Include/abstract.h

+32
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,38 @@ extern "C" {
6060
This is the equivalent of the Python expression: o.attr_name. */
6161

6262

63+
/* Implemented elsewhere:
64+
65+
int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result);
66+
67+
Variant of PyObject_GetAttr() which doesn't raise AttributeError
68+
if the attribute is not found.
69+
70+
If the attribute is found, return 1 and set *result to a new strong
71+
reference to the attribute.
72+
If the attribute is not found, return 0 and set *result to NULL;
73+
the AttributeError is silenced.
74+
If an error other than AttributeError is raised, return -1 and
75+
set *result to NULL.
76+
*/
77+
78+
79+
/* Implemented elsewhere:
80+
81+
int PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result);
82+
83+
Variant of PyObject_GetAttrString() which doesn't raise AttributeError
84+
if the attribute is not found.
85+
86+
If the attribute is found, return 1 and set *result to a new strong
87+
reference to the attribute.
88+
If the attribute is not found, return 0 and set *result to NULL;
89+
the AttributeError is silenced.
90+
If an error other than AttributeError is raised, return -1 and
91+
set *result to NULL.
92+
*/
93+
94+
6395
/* Implemented elsewhere:
6496
6597
int PyObject_SetAttrString(PyObject *o, const char *attr_name, PyObject *v);

Include/cpython/object.h

+1-11
Original file line numberDiff line numberDiff line change
@@ -294,17 +294,7 @@ PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *);
294294
PyAPI_FUNC(int) _PyObject_IsAbstract(PyObject *);
295295
PyAPI_FUNC(PyObject *) _PyObject_GetAttrId(PyObject *, _Py_Identifier *);
296296
PyAPI_FUNC(int) _PyObject_SetAttrId(PyObject *, _Py_Identifier *, PyObject *);
297-
/* Replacements of PyObject_GetAttr() and _PyObject_GetAttrId() which
298-
don't raise AttributeError.
299-
300-
Return 1 and set *result != NULL if an attribute is found.
301-
Return 0 and set *result == NULL if an attribute is not found;
302-
an AttributeError is silenced.
303-
Return -1 and set *result == NULL if an error other than AttributeError
304-
is raised.
305-
*/
306-
PyAPI_FUNC(int) _PyObject_LookupAttr(PyObject *, PyObject *, PyObject **);
307-
PyAPI_FUNC(int) _PyObject_LookupAttrId(PyObject *, _Py_Identifier *, PyObject **);
297+
#define _PyObject_LookupAttr PyObject_GetOptionalAttr
308298

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

Include/object.h

+4
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,10 @@ PyAPI_FUNC(int) PyObject_SetAttrString(PyObject *, const char *, PyObject *);
394394
PyAPI_FUNC(int) PyObject_DelAttrString(PyObject *v, const char *name);
395395
PyAPI_FUNC(int) PyObject_HasAttrString(PyObject *, const char *);
396396
PyAPI_FUNC(PyObject *) PyObject_GetAttr(PyObject *, PyObject *);
397+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030d0000
398+
PyAPI_FUNC(int) PyObject_GetOptionalAttr(PyObject *, PyObject *, PyObject **);
399+
PyAPI_FUNC(int) PyObject_GetOptionalAttrString(PyObject *, const char *, PyObject **);
400+
#endif
397401
PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *);
398402
PyAPI_FUNC(int) PyObject_DelAttr(PyObject *v, PyObject *name);
399403
PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *);

Lib/test/test_stable_abi_ctypes.py

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add :c:func:`PyObject_GetOptionalAttr` and :c:func:`PyObject_GetOptionalAttrString` functions.

Misc/stable_abi.toml

+4
Original file line numberDiff line numberDiff line change
@@ -2436,3 +2436,7 @@
24362436
added = '3.13'
24372437
[function.PyObject_DelAttrString]
24382438
added = '3.13'
2439+
[function.PyObject_GetOptionalAttr]
2440+
added = '3.13'
2441+
[function.PyObject_GetOptionalAttrString]
2442+
added = '3.13'

Objects/object.c

+24-10
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ _PyObject_FunctionStr(PyObject *x)
692692
{
693693
assert(!PyErr_Occurred());
694694
PyObject *qualname;
695-
int ret = _PyObject_LookupAttr(x, &_Py_ID(__qualname__), &qualname);
695+
int ret = PyObject_GetOptionalAttr(x, &_Py_ID(__qualname__), &qualname);
696696
if (qualname == NULL) {
697697
if (ret < 0) {
698698
return NULL;
@@ -701,7 +701,7 @@ _PyObject_FunctionStr(PyObject *x)
701701
}
702702
PyObject *module;
703703
PyObject *result = NULL;
704-
ret = _PyObject_LookupAttr(x, &_Py_ID(__module__), &module);
704+
ret = PyObject_GetOptionalAttr(x, &_Py_ID(__module__), &module);
705705
if (module != NULL && module != Py_None) {
706706
ret = PyObject_RichCompareBool(module, &_Py_ID(builtins), Py_NE);
707707
if (ret < 0) {
@@ -957,7 +957,7 @@ _PyObject_IsAbstract(PyObject *obj)
957957
if (obj == NULL)
958958
return 0;
959959

960-
res = _PyObject_LookupAttr(obj, &_Py_ID(__isabstractmethod__), &isabstract);
960+
res = PyObject_GetOptionalAttr(obj, &_Py_ID(__isabstractmethod__), &isabstract);
961961
if (res > 0) {
962962
res = PyObject_IsTrue(isabstract);
963963
Py_DECREF(isabstract);
@@ -1049,7 +1049,7 @@ PyObject_GetAttr(PyObject *v, PyObject *name)
10491049
}
10501050

10511051
int
1052-
_PyObject_LookupAttr(PyObject *v, PyObject *name, PyObject **result)
1052+
PyObject_GetOptionalAttr(PyObject *v, PyObject *name, PyObject **result)
10531053
{
10541054
PyTypeObject *tp = Py_TYPE(v);
10551055

@@ -1117,21 +1117,35 @@ _PyObject_LookupAttr(PyObject *v, PyObject *name, PyObject **result)
11171117
}
11181118

11191119
int
1120-
_PyObject_LookupAttrId(PyObject *v, _Py_Identifier *name, PyObject **result)
1120+
PyObject_GetOptionalAttrString(PyObject *obj, const char *name, PyObject **result)
11211121
{
1122-
PyObject *oname = _PyUnicode_FromId(name); /* borrowed */
1123-
if (!oname) {
1124-
*result = NULL;
1122+
if (Py_TYPE(obj)->tp_getattr == NULL) {
1123+
PyObject *oname = PyUnicode_FromString(name);
1124+
if (oname == NULL) {
1125+
*result = NULL;
1126+
return -1;
1127+
}
1128+
int rc = PyObject_GetOptionalAttr(obj, oname, result);
1129+
Py_DECREF(oname);
1130+
return rc;
1131+
}
1132+
1133+
*result = (*Py_TYPE(obj)->tp_getattr)(obj, (char*)name);
1134+
if (*result != NULL) {
1135+
return 1;
1136+
}
1137+
if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
11251138
return -1;
11261139
}
1127-
return _PyObject_LookupAttr(v, oname, result);
1140+
PyErr_Clear();
1141+
return 0;
11281142
}
11291143

11301144
int
11311145
PyObject_HasAttr(PyObject *v, PyObject *name)
11321146
{
11331147
PyObject *res;
1134-
if (_PyObject_LookupAttr(v, name, &res) < 0) {
1148+
if (PyObject_GetOptionalAttr(v, name, &res) < 0) {
11351149
PyErr_Clear();
11361150
return 0;
11371151
}

PC/python3dll.c

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)