diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst index 564d6cc42136da..e2f44a55b180b1 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 208b5c2ccf5cd2..8d1e0692a24221 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 d3e683d4a085fd..3e45d5d0f7f29d 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 00000000000000..1179ef49651bd8 --- /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 3bbd0ca9667b0d..6cfac2d787f171 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -3154,20 +3154,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) @@ -3188,8 +3189,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;