[Python-checkins] gh-106521: Add PyObject_GetOptionalAttr() function (GH-106522)

serhiy-storchaka webhook-mailer at python.org
Tue Jul 11 15:13:31 EDT 2023


https://github.com/python/cpython/commit/579aa89e68a6607398317a50586af781981e89fb
commit: 579aa89e68a6607398317a50586af781981e89fb
branch: main
author: Serhiy Storchaka <storchaka at gmail.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2023-07-11T22:13:27+03:00
summary:

gh-106521: Add PyObject_GetOptionalAttr() function (GH-106522)

It is a new name of former _PyObject_LookupAttr().

Add also PyObject_GetOptionalAttrString().

files:
A Misc/NEWS.d/next/C API/2023-07-07-19-14-00.gh-issue-106521.Veh9f3.rst
M Doc/c-api/object.rst
M Doc/data/stable_abi.dat
M Doc/whatsnew/3.13.rst
M Include/abstract.h
M Include/cpython/object.h
M Include/object.h
M Lib/test/test_stable_abi_ctypes.py
M Misc/stable_abi.toml
M Objects/object.c
M PC/python3dll.c

diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst
index 22e7458013fb3..6fc5b2d14dd32 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 9a61ddc39a353..d7e8b5b464a71 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 8fc809deca139..62981673140cf 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 <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.
diff --git a/Include/abstract.h b/Include/abstract.h
index c84d2c704e960..f47ea1ae3210e 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 ef9006431bee2..ba30c567c40eb 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 dccab07e5f2c6..7f2e4e90615e7 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 20bc2624c8136..6aa3cf382f9c6 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 0000000000000..f38fd271e8a44
--- /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 c61fedf8390e2..1f6519cb1e1b2 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 540ba5d07427c..d30e048335ab6 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 a7173911c7c1e..7d7192958eeb9 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)



More information about the Python-checkins mailing list