[Python-checkins] bpo-12022: Change error type for bad objects in "with" and "async with" (GH-26809)

serhiy-storchaka webhook-mailer at python.org
Tue Jun 29 04:27:28 EDT 2021


https://github.com/python/cpython/commit/20a88004bae8ead66a205a125e1fe979376fc3ea
commit: 20a88004bae8ead66a205a125e1fe979376fc3ea
branch: main
author: Serhiy Storchaka <storchaka at gmail.com>
committer: serhiy-storchaka <storchaka at gmail.com>
date: 2021-06-29T11:27:04+03:00
summary:

bpo-12022: Change error type for bad objects in "with" and "async with" (GH-26809)

A TypeError is now raised instead of an AttributeError in
"with" and "async with" statements for objects which do not
support the context manager or asynchronous context manager
protocols correspondingly.

files:
A Misc/NEWS.d/next/Core and Builtins/2021-06-20-10-53-21.bpo-12022.SW240M.rst
M Doc/whatsnew/3.11.rst
M Lib/test/test_contextlib.py
M Lib/test/test_coroutines.py
M Lib/test/test_with.py
M Python/ceval.c

diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst
index cc88c4166ec1a..be1423b9f44b3 100644
--- a/Doc/whatsnew/3.11.rst
+++ b/Doc/whatsnew/3.11.rst
@@ -76,6 +76,12 @@ Other Language Changes
 ======================
 
 
+* A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in 
+  :keyword:`with` and :keyword:`async with` statements for objects which do not
+  support the :term:`context manager` or :term:`asynchronous context manager` 
+  protocols correspondingly.
+  (Contributed by Serhiy Storchaka in :issue:`12022`.)
+
 
 New Modules
 ===========
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
index 453ef6c9f0832..dbc3f5fbe2b27 100644
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -491,7 +491,7 @@ def __unter__(self):
             def __exit__(self, *exc):
                 pass
 
-        with self.assertRaises(AttributeError):
+        with self.assertRaisesRegex(TypeError, 'the context manager'):
             with mycontext():
                 pass
 
@@ -503,7 +503,7 @@ def __enter__(self):
             def __uxit__(self, *exc):
                 pass
 
-        with self.assertRaises(AttributeError):
+        with self.assertRaisesRegex(TypeError, 'the context manager.*__exit__'):
             with mycontext():
                 pass
 
diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py
index a6a199e323c5f..9b83244b5006d 100644
--- a/Lib/test/test_coroutines.py
+++ b/Lib/test/test_coroutines.py
@@ -1212,7 +1212,7 @@ async def foo():
             async with CM():
                 body_executed = True
 
-        with self.assertRaisesRegex(AttributeError, '__aexit__'):
+        with self.assertRaisesRegex(TypeError, 'asynchronous context manager.*__aexit__'):
             run_async(foo())
         self.assertIs(body_executed, False)
 
@@ -1228,7 +1228,7 @@ async def foo():
             async with CM():
                 body_executed = True
 
-        with self.assertRaisesRegex(AttributeError, '__aenter__'):
+        with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
             run_async(foo())
         self.assertIs(body_executed, False)
 
@@ -1243,7 +1243,7 @@ async def foo():
             async with CM():
                 body_executed = True
 
-        with self.assertRaisesRegex(AttributeError, '__aenter__'):
+        with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
             run_async(foo())
         self.assertIs(body_executed, False)
 
diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py
index f21bf65fed849..07522bda6a558 100644
--- a/Lib/test/test_with.py
+++ b/Lib/test/test_with.py
@@ -117,7 +117,7 @@ def __exit__(self, type, value, traceback):
         def fooLacksEnter():
             foo = LacksEnter()
             with foo: pass
-        self.assertRaisesRegex(AttributeError, '__enter__', fooLacksEnter)
+        self.assertRaisesRegex(TypeError, 'the context manager', fooLacksEnter)
 
     def testEnterAttributeError2(self):
         class LacksEnterAndExit(object):
@@ -126,7 +126,7 @@ class LacksEnterAndExit(object):
         def fooLacksEnterAndExit():
             foo = LacksEnterAndExit()
             with foo: pass
-        self.assertRaisesRegex(AttributeError, '__enter__', fooLacksEnterAndExit)
+        self.assertRaisesRegex(TypeError, 'the context manager', fooLacksEnterAndExit)
 
     def testExitAttributeError(self):
         class LacksExit(object):
@@ -136,7 +136,7 @@ def __enter__(self):
         def fooLacksExit():
             foo = LacksExit()
             with foo: pass
-        self.assertRaisesRegex(AttributeError, '__exit__', fooLacksExit)
+        self.assertRaisesRegex(TypeError, 'the context manager.*__exit__', fooLacksExit)
 
     def assertRaisesSyntaxError(self, codestr):
         def shouldRaiseSyntaxError(s):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-06-20-10-53-21.bpo-12022.SW240M.rst b/Misc/NEWS.d/next/Core and Builtins/2021-06-20-10-53-21.bpo-12022.SW240M.rst
new file mode 100644
index 0000000000000..98c42283169d8
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-06-20-10-53-21.bpo-12022.SW240M.rst	
@@ -0,0 +1,4 @@
+A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in
+:keyword:`with` and :keyword:`async with` statements for objects which do
+not support the :term:`context manager` or :term:`asynchronous context
+manager` protocols correspondingly.
diff --git a/Python/ceval.c b/Python/ceval.c
index 3f961f60c081c..2ae36b3d28edd 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -82,7 +82,6 @@ static void format_exc_check_arg(PyThreadState *, PyObject *, const char *, PyOb
 static void format_exc_unbound(PyThreadState *tstate, PyCodeObject *co, int oparg);
 static PyObject * unicode_concatenate(PyThreadState *, PyObject *, PyObject *,
                                       PyFrameObject *, const _Py_CODEUNIT *);
-static PyObject * special_lookup(PyThreadState *, PyObject *, _Py_Identifier *);
 static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg);
 static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs);
 static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int);
@@ -3844,13 +3843,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
             _Py_IDENTIFIER(__aenter__);
             _Py_IDENTIFIER(__aexit__);
             PyObject *mgr = TOP();
-            PyObject *enter = special_lookup(tstate, mgr, &PyId___aenter__);
             PyObject *res;
+            PyObject *enter = _PyObject_LookupSpecial(mgr, &PyId___aenter__);
             if (enter == NULL) {
+                if (!_PyErr_Occurred(tstate)) {
+                    _PyErr_Format(tstate, PyExc_TypeError,
+                                  "'%.200s' object does not support the "
+                                  "asynchronous context manager protocol",
+                                  Py_TYPE(mgr)->tp_name);
+                }
                 goto error;
             }
-            PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__);
+            PyObject *exit = _PyObject_LookupSpecial(mgr, &PyId___aexit__);
             if (exit == NULL) {
+                if (!_PyErr_Occurred(tstate)) {
+                    _PyErr_Format(tstate, PyExc_TypeError,
+                                  "'%.200s' object does not support the "
+                                  "asynchronous context manager protocol "
+                                  "(missed __aexit__ method)",
+                                  Py_TYPE(mgr)->tp_name);
+                }
                 Py_DECREF(enter);
                 goto error;
             }
@@ -3869,13 +3881,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
             _Py_IDENTIFIER(__enter__);
             _Py_IDENTIFIER(__exit__);
             PyObject *mgr = TOP();
-            PyObject *enter = special_lookup(tstate, mgr, &PyId___enter__);
             PyObject *res;
+            PyObject *enter = _PyObject_LookupSpecial(mgr, &PyId___enter__);
             if (enter == NULL) {
+                if (!_PyErr_Occurred(tstate)) {
+                    _PyErr_Format(tstate, PyExc_TypeError,
+                                  "'%.200s' object does not support the "
+                                  "context manager protocol",
+                                  Py_TYPE(mgr)->tp_name);
+                }
                 goto error;
             }
-            PyObject *exit = special_lookup(tstate, mgr, &PyId___exit__);
+            PyObject *exit = _PyObject_LookupSpecial(mgr, &PyId___exit__);
             if (exit == NULL) {
+                if (!_PyErr_Occurred(tstate)) {
+                    _PyErr_Format(tstate, PyExc_TypeError,
+                                  "'%.200s' object does not support the "
+                                  "context manager protocol "
+                                  "(missed __exit__ method)",
+                                  Py_TYPE(mgr)->tp_name);
+                }
                 Py_DECREF(enter);
                 goto error;
             }
@@ -5110,19 +5135,6 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
 }
 
 
-static PyObject *
-special_lookup(PyThreadState *tstate, PyObject *o, _Py_Identifier *id)
-{
-    PyObject *res;
-    res = _PyObject_LookupSpecial(o, id);
-    if (res == NULL && !_PyErr_Occurred(tstate)) {
-        _PyErr_SetObject(tstate, PyExc_AttributeError, _PyUnicode_FromId(id));
-        return NULL;
-    }
-    return res;
-}
-
-
 /* Logic for the raise statement (too complicated for inlining).
    This *consumes* a reference count to each of its arguments. */
 static int



More information about the Python-checkins mailing list