Skip to content

Commit 1d1b97a

Browse files
geryogamncoghlan
authored andcommitted
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
1 parent 9af0e47 commit 1d1b97a

File tree

5 files changed

+27
-19
lines changed

5 files changed

+27
-19
lines changed

Doc/reference/compound_stmts.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -844,8 +844,8 @@ The following code::
844844
is semantically equivalent to::
845845

846846
manager = (EXPRESSION)
847-
aexit = type(manager).__aexit__
848847
aenter = type(manager).__aenter__
848+
aexit = type(manager).__aexit__
849849
value = await aenter(manager)
850850
hit_except = False
851851

Lib/test/test_coroutines.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,39 +1203,41 @@ class CM:
12031203
def __aenter__(self):
12041204
pass
12051205

1206+
body_executed = False
12061207
async def foo():
12071208
async with CM():
1208-
pass
1209+
body_executed = True
12091210

12101211
with self.assertRaisesRegex(AttributeError, '__aexit__'):
12111212
run_async(foo())
1213+
self.assertFalse(body_executed)
12121214

12131215
def test_with_3(self):
12141216
class CM:
12151217
def __aexit__(self):
12161218
pass
12171219

1220+
body_executed = False
12181221
async def foo():
12191222
async with CM():
1220-
pass
1223+
body_executed = True
12211224

12221225
with self.assertRaisesRegex(AttributeError, '__aenter__'):
12231226
run_async(foo())
1227+
self.assertFalse(body_executed)
12241228

12251229
def test_with_4(self):
12261230
class CM:
1227-
def __enter__(self):
1228-
pass
1229-
1230-
def __exit__(self):
1231-
pass
1231+
pass
12321232

1233+
body_executed = False
12331234
async def foo():
12341235
async with CM():
1235-
pass
1236+
body_executed = True
12361237

1237-
with self.assertRaisesRegex(AttributeError, '__aexit__'):
1238+
with self.assertRaisesRegex(AttributeError, '__aenter__'):
12381239
run_async(foo())
1240+
self.assertFalse(body_executed)
12391241

12401242
def test_with_5(self):
12411243
# While this test doesn't make a lot of sense,

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,7 @@ Elena Oat
12191219
Jon Oberheide
12201220
Milan Oberkirch
12211221
Pascal Oberndoerfer
1222+
Géry Ogam
12221223
Jeffrey Ollie
12231224
Adam Olsen
12241225
Bryan Olson
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Improve the displayed error message when incorrect types are passed to ``async
2+
with`` statements by looking up the :meth:`__aenter__` special method before
3+
the :meth:`__aexit__` special method when entering an asynchronous context
4+
manager. Patch by Géry Ogam.

Python/ceval.c

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3230,20 +3230,21 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
32303230
}
32313231

32323232
case TARGET(BEFORE_ASYNC_WITH): {
3233-
_Py_IDENTIFIER(__aexit__);
32343233
_Py_IDENTIFIER(__aenter__);
3235-
3234+
_Py_IDENTIFIER(__aexit__);
32363235
PyObject *mgr = TOP();
3237-
PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__),
3238-
*enter;
3236+
PyObject *enter = special_lookup(tstate, mgr, &PyId___aenter__);
32393237
PyObject *res;
3240-
if (exit == NULL)
3238+
if (enter == NULL) {
3239+
goto error;
3240+
}
3241+
PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__);
3242+
if (exit == NULL) {
3243+
Py_DECREF(enter);
32413244
goto error;
3245+
}
32423246
SET_TOP(exit);
3243-
enter = special_lookup(tstate, mgr, &PyId___aenter__);
32443247
Py_DECREF(mgr);
3245-
if (enter == NULL)
3246-
goto error;
32473248
res = _PyObject_CallNoArg(enter);
32483249
Py_DECREF(enter);
32493250
if (res == NULL)
@@ -3264,8 +3265,8 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
32643265
}
32653266

32663267
case TARGET(SETUP_WITH): {
3267-
_Py_IDENTIFIER(__exit__);
32683268
_Py_IDENTIFIER(__enter__);
3269+
_Py_IDENTIFIER(__exit__);
32693270
PyObject *mgr = TOP();
32703271
PyObject *enter = special_lookup(tstate, mgr, &PyId___enter__);
32713272
PyObject *res;

0 commit comments

Comments
 (0)