[Python-checkins] bpo-39048: Look up __aenter__ before __aexit__ in async with (GH-17609)

Nick Coghlan webhook-mailer at python.org
Tue Jan 14 06:58:39 EST 2020


https://github.com/python/cpython/commit/1d1b97ae643dd8b22d87785ed7bd2599c6c8dc8d
commit: 1d1b97ae643dd8b22d87785ed7bd2599c6c8dc8d
branch: master
author: Géry Ogam <gery.ogam at gmail.com>
committer: Nick Coghlan <ncoghlan at gmail.com>
date: 2020-01-14T21:58:29+10:00
summary:

bpo-39048: Look up __aenter__ before __aexit__ in async with (GH-17609)

* Reorder the __aenter__ and __aexit__ checks for async with
* Add assertions for async with body being skipped
* Swap __aexit__ and __aenter__ loading in the documentation

files:
A Misc/NEWS.d/next/Core and Builtins/2020-01-13-14-45-22.bpo-39048.iPsj81.rst
M Doc/reference/compound_stmts.rst
M Lib/test/test_coroutines.py
M Misc/ACKS
M Python/ceval.c

diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst
index 564d6cc42136d..e2f44a55b180b 100644
--- a/Doc/reference/compound_stmts.rst
+++ b/Doc/reference/compound_stmts.rst
@@ -844,8 +844,8 @@ The following code::
 is semantically equivalent to::
 
     manager = (EXPRESSION)
-    aexit = type(manager).__aexit__
     aenter = type(manager).__aenter__
+    aexit = type(manager).__aexit__
     value = await aenter(manager)
     hit_except = False
 
diff --git a/Lib/test/test_coroutines.py b/Lib/test/test_coroutines.py
index 208b5c2ccf5cd..8d1e0692a2422 100644
--- a/Lib/test/test_coroutines.py
+++ b/Lib/test/test_coroutines.py
@@ -1203,39 +1203,41 @@ class CM:
             def __aenter__(self):
                 pass
 
+        body_executed = False
         async def foo():
             async with CM():
-                pass
+                body_executed = True
 
         with self.assertRaisesRegex(AttributeError, '__aexit__'):
             run_async(foo())
+        self.assertFalse(body_executed)
 
     def test_with_3(self):
         class CM:
             def __aexit__(self):
                 pass
 
+        body_executed = False
         async def foo():
             async with CM():
-                pass
+                body_executed = True
 
         with self.assertRaisesRegex(AttributeError, '__aenter__'):
             run_async(foo())
+        self.assertFalse(body_executed)
 
     def test_with_4(self):
         class CM:
-            def __enter__(self):
-                pass
-
-            def __exit__(self):
-                pass
+            pass
 
+        body_executed = False
         async def foo():
             async with CM():
-                pass
+                body_executed = True
 
-        with self.assertRaisesRegex(AttributeError, '__aexit__'):
+        with self.assertRaisesRegex(AttributeError, '__aenter__'):
             run_async(foo())
+        self.assertFalse(body_executed)
 
     def test_with_5(self):
         # While this test doesn't make a lot of sense,
diff --git a/Misc/ACKS b/Misc/ACKS
index d3e683d4a085f..3e45d5d0f7f29 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1219,6 +1219,7 @@ Elena Oat
 Jon Oberheide
 Milan Oberkirch
 Pascal Oberndoerfer
+Géry Ogam
 Jeffrey Ollie
 Adam Olsen
 Bryan Olson
diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-01-13-14-45-22.bpo-39048.iPsj81.rst b/Misc/NEWS.d/next/Core and Builtins/2020-01-13-14-45-22.bpo-39048.iPsj81.rst
new file mode 100644
index 0000000000000..1179ef49651bd
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2020-01-13-14-45-22.bpo-39048.iPsj81.rst	
@@ -0,0 +1,4 @@
+Improve the displayed error message when incorrect types are passed to ``async
+with`` statements by looking up the :meth:`__aenter__` special method before
+the :meth:`__aexit__` special method when entering an asynchronous context
+manager. Patch by Géry Ogam.
diff --git a/Python/ceval.c b/Python/ceval.c
index 096645aeebfb9..5e586589e9618 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3230,20 +3230,21 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
         }
 
         case TARGET(BEFORE_ASYNC_WITH): {
-            _Py_IDENTIFIER(__aexit__);
             _Py_IDENTIFIER(__aenter__);
-
+            _Py_IDENTIFIER(__aexit__);
             PyObject *mgr = TOP();
-            PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__),
-                     *enter;
+            PyObject *enter = special_lookup(tstate, mgr, &PyId___aenter__);
             PyObject *res;
-            if (exit == NULL)
+            if (enter == NULL) {
+                goto error;
+            }
+            PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__);
+            if (exit == NULL) {
+                Py_DECREF(enter);
                 goto error;
+            }
             SET_TOP(exit);
-            enter = special_lookup(tstate, mgr, &PyId___aenter__);
             Py_DECREF(mgr);
-            if (enter == NULL)
-                goto error;
             res = _PyObject_CallNoArg(enter);
             Py_DECREF(enter);
             if (res == NULL)
@@ -3264,8 +3265,8 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
         }
 
         case TARGET(SETUP_WITH): {
-            _Py_IDENTIFIER(__exit__);
             _Py_IDENTIFIER(__enter__);
+            _Py_IDENTIFIER(__exit__);
             PyObject *mgr = TOP();
             PyObject *enter = special_lookup(tstate, mgr, &PyId___enter__);
             PyObject *res;



More information about the Python-checkins mailing list