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:: is semantically equivalent to::
manager = (EXPRESSION) manager = (EXPRESSION)
aexit = type(manager).__aexit__
aenter = type(manager).__aenter__ aenter = type(manager).__aenter__
aexit = type(manager).__aexit__
value = await aenter(manager) value = await aenter(manager)
hit_except = False hit_except = False

View File

@ -1203,39 +1203,41 @@ class CoroutineTest(unittest.TestCase):
def __aenter__(self): def __aenter__(self):
pass pass
body_executed = False
async def foo(): async def foo():
async with CM(): async with CM():
pass body_executed = True
with self.assertRaisesRegex(AttributeError, '__aexit__'): with self.assertRaisesRegex(AttributeError, '__aexit__'):
run_async(foo()) run_async(foo())
self.assertFalse(body_executed)
def test_with_3(self): def test_with_3(self):
class CM: class CM:
def __aexit__(self): def __aexit__(self):
pass pass
body_executed = False
async def foo(): async def foo():
async with CM(): async with CM():
pass body_executed = True
with self.assertRaisesRegex(AttributeError, '__aenter__'): with self.assertRaisesRegex(AttributeError, '__aenter__'):
run_async(foo()) run_async(foo())
self.assertFalse(body_executed)
def test_with_4(self): def test_with_4(self):
class CM: class CM:
def __enter__(self): pass
pass
def __exit__(self):
pass
body_executed = False
async def foo(): async def foo():
async with CM(): async with CM():
pass body_executed = True
with self.assertRaisesRegex(AttributeError, '__aexit__'): with self.assertRaisesRegex(AttributeError, '__aenter__'):
run_async(foo()) run_async(foo())
self.assertFalse(body_executed)
def test_with_5(self): def test_with_5(self):
# While this test doesn't make a lot of sense, # While this test doesn't make a lot of sense,

View File

@ -1219,6 +1219,7 @@ Elena Oat
Jon Oberheide Jon Oberheide
Milan Oberkirch Milan Oberkirch
Pascal Oberndoerfer Pascal Oberndoerfer
Géry Ogam
Jeffrey Ollie Jeffrey Ollie
Adam Olsen Adam Olsen
Bryan Olson 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): { case TARGET(BEFORE_ASYNC_WITH): {
_Py_IDENTIFIER(__aexit__);
_Py_IDENTIFIER(__aenter__); _Py_IDENTIFIER(__aenter__);
_Py_IDENTIFIER(__aexit__);
PyObject *mgr = TOP(); PyObject *mgr = TOP();
PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__), PyObject *enter = special_lookup(tstate, mgr, &PyId___aenter__);
*enter;
PyObject *res; PyObject *res;
if (exit == NULL) if (enter == NULL) {
goto error; goto error;
}
PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__);
if (exit == NULL) {
Py_DECREF(enter);
goto error;
}
SET_TOP(exit); SET_TOP(exit);
enter = special_lookup(tstate, mgr, &PyId___aenter__);
Py_DECREF(mgr); Py_DECREF(mgr);
if (enter == NULL)
goto error;
res = _PyObject_CallNoArg(enter); res = _PyObject_CallNoArg(enter);
Py_DECREF(enter); Py_DECREF(enter);
if (res == NULL) if (res == NULL)
@ -3264,8 +3265,8 @@ main_loop:
} }
case TARGET(SETUP_WITH): { case TARGET(SETUP_WITH): {
_Py_IDENTIFIER(__exit__);
_Py_IDENTIFIER(__enter__); _Py_IDENTIFIER(__enter__);
_Py_IDENTIFIER(__exit__);
PyObject *mgr = TOP(); PyObject *mgr = TOP();
PyObject *enter = special_lookup(tstate, mgr, &PyId___enter__); PyObject *enter = special_lookup(tstate, mgr, &PyId___enter__);
PyObject *res; PyObject *res;