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
This commit is contained in:
Géry Ogam 2020-01-14 12:58:29 +01:00 committed by Nick Coghlan
parent 9af0e47b17
commit 1d1b97ae64
5 changed files with 27 additions and 19 deletions

View File

@ -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

View File

@ -1203,39 +1203,41 @@ class CoroutineTest(unittest.TestCase):
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,

View File

@ -1219,6 +1219,7 @@ Elena Oat
Jon Oberheide
Milan Oberkirch
Pascal Oberndoerfer
Géry Ogam
Jeffrey Ollie
Adam Olsen
Bryan Olson

View File

@ -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.

View File

@ -3230,20 +3230,21 @@ main_loop:
}
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 @@ main_loop:
}
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;