mirror of
https://github.com/python/cpython.git
synced 2026-01-06 15:32:22 +00:00
bpo-12022: Change error type for bad objects in "with" and "async with" (GH-26809)
A TypeError is now raised instead of an AttributeError in "with" and "async with" statements for objects which do not support the context manager or asynchronous context manager protocols correspondingly.
This commit is contained in:
parent
48e3a1d95a
commit
20a88004ba
6 changed files with 48 additions and 26 deletions
|
|
@ -76,6 +76,12 @@ Other Language Changes
|
|||
======================
|
||||
|
||||
|
||||
* A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in
|
||||
:keyword:`with` and :keyword:`async with` statements for objects which do not
|
||||
support the :term:`context manager` or :term:`asynchronous context manager`
|
||||
protocols correspondingly.
|
||||
(Contributed by Serhiy Storchaka in :issue:`12022`.)
|
||||
|
||||
|
||||
New Modules
|
||||
===========
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ def __unter__(self):
|
|||
def __exit__(self, *exc):
|
||||
pass
|
||||
|
||||
with self.assertRaises(AttributeError):
|
||||
with self.assertRaisesRegex(TypeError, 'the context manager'):
|
||||
with mycontext():
|
||||
pass
|
||||
|
||||
|
|
@ -503,7 +503,7 @@ def __enter__(self):
|
|||
def __uxit__(self, *exc):
|
||||
pass
|
||||
|
||||
with self.assertRaises(AttributeError):
|
||||
with self.assertRaisesRegex(TypeError, 'the context manager.*__exit__'):
|
||||
with mycontext():
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -1212,7 +1212,7 @@ async def foo():
|
|||
async with CM():
|
||||
body_executed = True
|
||||
|
||||
with self.assertRaisesRegex(AttributeError, '__aexit__'):
|
||||
with self.assertRaisesRegex(TypeError, 'asynchronous context manager.*__aexit__'):
|
||||
run_async(foo())
|
||||
self.assertIs(body_executed, False)
|
||||
|
||||
|
|
@ -1228,7 +1228,7 @@ async def foo():
|
|||
async with CM():
|
||||
body_executed = True
|
||||
|
||||
with self.assertRaisesRegex(AttributeError, '__aenter__'):
|
||||
with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
|
||||
run_async(foo())
|
||||
self.assertIs(body_executed, False)
|
||||
|
||||
|
|
@ -1243,7 +1243,7 @@ async def foo():
|
|||
async with CM():
|
||||
body_executed = True
|
||||
|
||||
with self.assertRaisesRegex(AttributeError, '__aenter__'):
|
||||
with self.assertRaisesRegex(TypeError, 'asynchronous context manager'):
|
||||
run_async(foo())
|
||||
self.assertIs(body_executed, False)
|
||||
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ def __exit__(self, type, value, traceback):
|
|||
def fooLacksEnter():
|
||||
foo = LacksEnter()
|
||||
with foo: pass
|
||||
self.assertRaisesRegex(AttributeError, '__enter__', fooLacksEnter)
|
||||
self.assertRaisesRegex(TypeError, 'the context manager', fooLacksEnter)
|
||||
|
||||
def testEnterAttributeError2(self):
|
||||
class LacksEnterAndExit(object):
|
||||
|
|
@ -126,7 +126,7 @@ class LacksEnterAndExit(object):
|
|||
def fooLacksEnterAndExit():
|
||||
foo = LacksEnterAndExit()
|
||||
with foo: pass
|
||||
self.assertRaisesRegex(AttributeError, '__enter__', fooLacksEnterAndExit)
|
||||
self.assertRaisesRegex(TypeError, 'the context manager', fooLacksEnterAndExit)
|
||||
|
||||
def testExitAttributeError(self):
|
||||
class LacksExit(object):
|
||||
|
|
@ -136,7 +136,7 @@ def __enter__(self):
|
|||
def fooLacksExit():
|
||||
foo = LacksExit()
|
||||
with foo: pass
|
||||
self.assertRaisesRegex(AttributeError, '__exit__', fooLacksExit)
|
||||
self.assertRaisesRegex(TypeError, 'the context manager.*__exit__', fooLacksExit)
|
||||
|
||||
def assertRaisesSyntaxError(self, codestr):
|
||||
def shouldRaiseSyntaxError(s):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
A :exc:`TypeError` is now raised instead of an :exc:`AttributeError` in
|
||||
:keyword:`with` and :keyword:`async with` statements for objects which do
|
||||
not support the :term:`context manager` or :term:`asynchronous context
|
||||
manager` protocols correspondingly.
|
||||
|
|
@ -82,7 +82,6 @@ static void format_exc_check_arg(PyThreadState *, PyObject *, const char *, PyOb
|
|||
static void format_exc_unbound(PyThreadState *tstate, PyCodeObject *co, int oparg);
|
||||
static PyObject * unicode_concatenate(PyThreadState *, PyObject *, PyObject *,
|
||||
PyFrameObject *, const _Py_CODEUNIT *);
|
||||
static PyObject * special_lookup(PyThreadState *, PyObject *, _Py_Identifier *);
|
||||
static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg);
|
||||
static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs);
|
||||
static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int);
|
||||
|
|
@ -3844,13 +3843,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
|
|||
_Py_IDENTIFIER(__aenter__);
|
||||
_Py_IDENTIFIER(__aexit__);
|
||||
PyObject *mgr = TOP();
|
||||
PyObject *enter = special_lookup(tstate, mgr, &PyId___aenter__);
|
||||
PyObject *res;
|
||||
PyObject *enter = _PyObject_LookupSpecial(mgr, &PyId___aenter__);
|
||||
if (enter == NULL) {
|
||||
if (!_PyErr_Occurred(tstate)) {
|
||||
_PyErr_Format(tstate, PyExc_TypeError,
|
||||
"'%.200s' object does not support the "
|
||||
"asynchronous context manager protocol",
|
||||
Py_TYPE(mgr)->tp_name);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
PyObject *exit = special_lookup(tstate, mgr, &PyId___aexit__);
|
||||
PyObject *exit = _PyObject_LookupSpecial(mgr, &PyId___aexit__);
|
||||
if (exit == NULL) {
|
||||
if (!_PyErr_Occurred(tstate)) {
|
||||
_PyErr_Format(tstate, PyExc_TypeError,
|
||||
"'%.200s' object does not support the "
|
||||
"asynchronous context manager protocol "
|
||||
"(missed __aexit__ method)",
|
||||
Py_TYPE(mgr)->tp_name);
|
||||
}
|
||||
Py_DECREF(enter);
|
||||
goto error;
|
||||
}
|
||||
|
|
@ -3869,13 +3881,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
|
|||
_Py_IDENTIFIER(__enter__);
|
||||
_Py_IDENTIFIER(__exit__);
|
||||
PyObject *mgr = TOP();
|
||||
PyObject *enter = special_lookup(tstate, mgr, &PyId___enter__);
|
||||
PyObject *res;
|
||||
PyObject *enter = _PyObject_LookupSpecial(mgr, &PyId___enter__);
|
||||
if (enter == NULL) {
|
||||
if (!_PyErr_Occurred(tstate)) {
|
||||
_PyErr_Format(tstate, PyExc_TypeError,
|
||||
"'%.200s' object does not support the "
|
||||
"context manager protocol",
|
||||
Py_TYPE(mgr)->tp_name);
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
PyObject *exit = special_lookup(tstate, mgr, &PyId___exit__);
|
||||
PyObject *exit = _PyObject_LookupSpecial(mgr, &PyId___exit__);
|
||||
if (exit == NULL) {
|
||||
if (!_PyErr_Occurred(tstate)) {
|
||||
_PyErr_Format(tstate, PyExc_TypeError,
|
||||
"'%.200s' object does not support the "
|
||||
"context manager protocol "
|
||||
"(missed __exit__ method)",
|
||||
Py_TYPE(mgr)->tp_name);
|
||||
}
|
||||
Py_DECREF(enter);
|
||||
goto error;
|
||||
}
|
||||
|
|
@ -5110,19 +5135,6 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
|
|||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
special_lookup(PyThreadState *tstate, PyObject *o, _Py_Identifier *id)
|
||||
{
|
||||
PyObject *res;
|
||||
res = _PyObject_LookupSpecial(o, id);
|
||||
if (res == NULL && !_PyErr_Occurred(tstate)) {
|
||||
_PyErr_SetObject(tstate, PyExc_AttributeError, _PyUnicode_FromId(id));
|
||||
return NULL;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/* Logic for the raise statement (too complicated for inlining).
|
||||
This *consumes* a reference count to each of its arguments. */
|
||||
static int
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue