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
diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst
index 564d6cc..e2f44a5 100644
--- a/Doc/reference/compound_stmts.rst
+++ b/Doc/reference/compound_stmts.rst
@@ -844,8 +844,8 @@
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 208b5c2..8d1e069 100644
--- a/Lib/test/test_coroutines.py
+++ b/Lib/test/test_coroutines.py
@@ -1203,39 +1203,41 @@
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
+ pass
- def __exit__(self):
- 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 d3e683d..3e45d5d 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1219,6 +1219,7 @@
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 0000000..1179ef4
--- /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 096645a..5e58658 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3230,20 +3230,21 @@
}
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 @@
}
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;