bpo-32571: Avoid raising unneeded AttributeError and silencing it in C code (GH-5222)

Add two new private APIs: _PyObject_LookupAttr() and _PyObject_LookupAttrId()
diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c
index b81abde..2b7aaaf 100644
--- a/Modules/_io/bufferedio.c
+++ b/Modules/_io/bufferedio.c
@@ -1541,7 +1541,9 @@
     }
     _bufferedreader_reset_buf(self);
 
-    readall = _PyObject_GetAttrWithoutError(self->raw, _PyIO_str_readall);
+    if (_PyObject_LookupAttr(self->raw, _PyIO_str_readall, &readall) < 0) {
+        goto cleanup;
+    }
     if (readall) {
         tmp = _PyObject_CallNoArg(readall);
         Py_DECREF(readall);
@@ -1561,9 +1563,6 @@
         }
         goto cleanup;
     }
-    else if (PyErr_Occurred()) {
-        goto cleanup;
-    }
 
     chunks = PyList_New(0);
     if (chunks == NULL)
diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c
index 269142c..0d5bf3b 100644
--- a/Modules/_io/fileio.c
+++ b/Modules/_io/fileio.c
@@ -1073,12 +1073,10 @@
     if (self->fd < 0)
         return PyUnicode_FromFormat("<_io.FileIO [closed]>");
 
-    nameobj = _PyObject_GetAttrId((PyObject *) self, &PyId_name);
+    if (_PyObject_LookupAttrId((PyObject *) self, &PyId_name, &nameobj) < 0) {
+        return NULL;
+    }
     if (nameobj == NULL) {
-        if (PyErr_ExceptionMatches(PyExc_AttributeError))
-            PyErr_Clear();
-        else
-            return NULL;
         res = PyUnicode_FromFormat(
             "<_io.FileIO fd=%d mode='%s' closefd=%s>",
             self->fd, mode_string(self), self->closefd ? "True" : "False");
diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c
index 348d449..0c329bf 100644
--- a/Modules/_io/iobase.c
+++ b/Modules/_io/iobase.c
@@ -133,18 +133,12 @@
 iobase_is_closed(PyObject *self)
 {
     PyObject *res;
+    int ret;
     /* This gets the derived attribute, which is *not* __IOBase_closed
        in most cases! */
-    res = _PyObject_GetAttrId(self, &PyId___IOBase_closed);
-    if (res == NULL) {
-        if (!PyErr_ExceptionMatches(PyExc_AttributeError)) {
-            return -1;
-        }
-        PyErr_Clear();
-        return 0;
-    }
-    Py_DECREF(res);
-    return 1;
+    ret = _PyObject_LookupAttrId(self, &PyId___IOBase_closed, &res);
+    Py_XDECREF(res);
+    return ret;
 }
 
 /* Flush and close methods */
@@ -190,20 +184,16 @@
     int closed;
     /* This gets the derived attribute, which is *not* __IOBase_closed
        in most cases! */
-    res = _PyObject_GetAttrWithoutError(self, _PyIO_str_closed);
-    if (res == NULL) {
-        if (PyErr_Occurred()) {
+    closed = _PyObject_LookupAttr(self, _PyIO_str_closed, &res);
+    if (closed > 0) {
+        closed = PyObject_IsTrue(res);
+        Py_DECREF(res);
+        if (closed > 0) {
+            PyErr_SetString(PyExc_ValueError, "I/O operation on closed file.");
             return -1;
         }
-        return 0;
     }
-    closed = PyObject_IsTrue(res);
-    Py_DECREF(res);
-    if (closed <= 0) {
-        return closed;
-    }
-    PyErr_SetString(PyExc_ValueError, "I/O operation on closed file.");
-    return -1;
+    return closed;
 }
 
 PyObject *
@@ -273,8 +263,7 @@
 
     /* If `closed` doesn't exist or can't be evaluated as bool, then the
        object is probably in an unusable state, so ignore. */
-    res = _PyObject_GetAttrWithoutError(self, _PyIO_str_closed);
-    if (res == NULL) {
+    if (_PyObject_LookupAttr(self, _PyIO_str_closed, &res) <= 0) {
         PyErr_Clear();
         closed = -1;
     }
@@ -538,8 +527,7 @@
     PyObject *peek, *buffer, *result;
     Py_ssize_t old_size = -1;
 
-    peek = _PyObject_GetAttrWithoutError(self, _PyIO_str_peek);
-    if (peek == NULL && PyErr_Occurred()) {
+    if (_PyObject_LookupAttr(self, _PyIO_str_peek, &peek) < 0) {
         return NULL;
     }
 
diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
index 9f3fd2d..717b56a 100644
--- a/Modules/_io/textio.c
+++ b/Modules/_io/textio.c
@@ -924,14 +924,10 @@
         return -1;
 
     /* Get the normalized named of the codec */
-    res = _PyObject_GetAttrId(codec_info, &PyId_name);
-    if (res == NULL) {
-        if (PyErr_ExceptionMatches(PyExc_AttributeError))
-            PyErr_Clear();
-        else
-            return -1;
+    if (_PyObject_LookupAttrId(codec_info, &PyId_name, &res) < 0) {
+        return -1;
     }
-    else if (PyUnicode_Check(res)) {
+    if (res != NULL && PyUnicode_Check(res)) {
         const encodefuncentry *e = encodefuncs;
         while (e->name != NULL) {
             if (_PyUnicode_EqualToASCIIString(res, e->name)) {
@@ -1177,19 +1173,17 @@
 
     if (Py_TYPE(buffer) == &PyBufferedReader_Type ||
         Py_TYPE(buffer) == &PyBufferedWriter_Type ||
-        Py_TYPE(buffer) == &PyBufferedRandom_Type) {
-        raw = _PyObject_GetAttrId(buffer, &PyId_raw);
+        Py_TYPE(buffer) == &PyBufferedRandom_Type)
+    {
+        if (_PyObject_LookupAttrId(buffer, &PyId_raw, &raw) < 0)
+            goto error;
         /* Cache the raw FileIO object to speed up 'closed' checks */
-        if (raw == NULL) {
-            if (PyErr_ExceptionMatches(PyExc_AttributeError))
-                PyErr_Clear();
+        if (raw != NULL) {
+            if (Py_TYPE(raw) == &PyFileIO_Type)
+                self->raw = raw;
             else
-                goto error;
+                Py_DECREF(raw);
         }
-        else if (Py_TYPE(raw) == &PyFileIO_Type)
-            self->raw = raw;
-        else
-            Py_DECREF(raw);
     }
 
     res = _PyObject_CallMethodId(buffer, &PyId_seekable, NULL);
@@ -1201,17 +1195,12 @@
         goto error;
     self->seekable = self->telling = r;
 
-    res = _PyObject_GetAttrWithoutError(buffer, _PyIO_str_read1);
-    if (res != NULL) {
-        Py_DECREF(res);
-        self->has_read1 = 1;
-    }
-    else if (!PyErr_Occurred()) {
-        self->has_read1 = 0;
-    }
-    else {
+    r = _PyObject_LookupAttr(buffer, _PyIO_str_read1, &res);
+    if (r < 0) {
         goto error;
     }
+    Py_XDECREF(res);
+    self->has_read1 = r;
 
     self->encoding_start_of_stream = 0;
     if (_textiowrapper_fix_encoder_state(self) < 0) {
@@ -3020,10 +3009,9 @@
 {
     PyObject *res;
     CHECK_ATTACHED(self);
-    if (self->decoder == NULL)
-        Py_RETURN_NONE;
-    res = _PyObject_GetAttrWithoutError(self->decoder, _PyIO_str_newlines);
-    if (res == NULL && !PyErr_Occurred()) {
+    if (self->decoder == NULL ||
+        _PyObject_LookupAttr(self->decoder, _PyIO_str_newlines, &res) == 0)
+    {
         Py_RETURN_NONE;
     }
     return res;