Um, I thought I'd already checked this in.

Anyway, this is the changes to the with-statement
so that __exit__ must return a true value in order
for a pending exception to be ignored.
The PEP (343) is already updated.
This commit is contained in:
Guido van Rossum 2006-03-10 02:28:35 +00:00
parent 692cdbc5d6
commit f669436189
11 changed files with 61 additions and 106 deletions

View File

@ -779,7 +779,7 @@ class StackDepthTracker:
'SETUP_EXCEPT': 3,
'SETUP_FINALLY': 3,
'FOR_ITER': 1,
'WITH_CLEANUP': 3,
'WITH_CLEANUP': -1,
}
# use pattern match
patterns = [

View File

@ -858,8 +858,6 @@ class CodeGenerator:
self.nextBlock(final)
self.setups.push((END_FINALLY, final))
self.emit('WITH_CLEANUP')
self.emit('CALL_FUNCTION', 3)
self.emit('POP_TOP')
self.emit('END_FINALLY')
self.setups.pop()
self.__with_count -= 1

View File

@ -30,8 +30,9 @@ class GeneratorContextManager(object):
else:
try:
self.gen.throw(type, value, traceback)
return True
except StopIteration:
pass
return True
def contextmanager(func):
@ -91,6 +92,7 @@ def nested(*contexts):
"""
exits = []
vars = []
exc = (None, None, None)
try:
try:
for context in contexts:
@ -102,17 +104,14 @@ def nested(*contexts):
yield vars
except:
exc = sys.exc_info()
else:
exc = (None, None, None)
finally:
while exits:
exit = exits.pop()
try:
exit(*exc)
if exit(*exc):
exc = (None, None, None)
except:
exc = sys.exc_info()
else:
exc = (None, None, None)
if exc != (None, None, None):
raise

View File

@ -2196,8 +2196,6 @@ class ContextManager(object):
return self.new_context
def __exit__(self, t, v, tb):
setcontext(self.saved_context)
if t is not None:
raise t, v, tb
class Context(object):
"""Contains the context for a Decimal instance.

View File

@ -78,8 +78,8 @@ class Nested(object):
vars.append(mgr.__enter__())
self.entered.appendleft(mgr)
except:
self.__exit__(*sys.exc_info())
raise
if not self.__exit__(*sys.exc_info()):
raise
return vars
def __exit__(self, *exc_info):
@ -89,7 +89,8 @@ class Nested(object):
ex = exc_info
for mgr in self.entered:
try:
mgr.__exit__(*ex)
if mgr.__exit__(*ex):
ex = (None, None, None)
except:
ex = sys.exc_info()
self.entered = None
@ -574,9 +575,7 @@ class AssignmentTargetTestCase(unittest.TestCase):
class C:
def __context__(self): return self
def __enter__(self): return 1, 2, 3
def __exit__(self, t, v, tb):
if t is not None:
raise t, v, tb
def __exit__(self, t, v, tb): pass
targets = {1: [0, 1, 2]}
with C() as (targets[1][0], targets[1][1], targets[1][2]):
self.assertEqual(targets, {1: [1, 2, 3]})
@ -594,17 +593,30 @@ class AssignmentTargetTestCase(unittest.TestCase):
class ExitSwallowsExceptionTestCase(unittest.TestCase):
def testExitSwallowsException(self):
class AfricanOrEuropean:
def testExitTrueSwallowsException(self):
class AfricanSwallow:
def __context__(self): return self
def __enter__(self): pass
def __exit__(self, t, v, tb): pass
def __exit__(self, t, v, tb): return True
try:
with AfricanOrEuropean():
with AfricanSwallow():
1/0
except ZeroDivisionError:
self.fail("ZeroDivisionError should have been swallowed")
def testExitFalseDoesntSwallowException(self):
class EuropeanSwallow:
def __context__(self): return self
def __enter__(self): pass
def __exit__(self, t, v, tb): return False
try:
with EuropeanSwallow():
1/0
except ZeroDivisionError:
pass
else:
self.fail("ZeroDivisionError should have been raised")
def test_main():
run_unittest(FailureTestCase, NonexceptionalTestCase,

View File

@ -128,8 +128,6 @@ class _RLock(_Verbose):
def __exit__(self, t, v, tb):
self.release()
if t is not None:
raise t, v, tb
# Internal methods used by condition variables
@ -190,8 +188,6 @@ class _Condition(_Verbose):
def __exit__(self, t, v, tb):
self.release()
if t is not None:
raise t, v, tb
def __repr__(self):
return "<Condition(%s, %d)>" % (self.__lock, len(self.__waiters))
@ -321,8 +317,6 @@ class _Semaphore(_Verbose):
def __exit__(self, t, v, tb):
self.release()
if t is not None:
raise t, v, tb
def BoundedSemaphore(*args, **kwargs):

View File

@ -68,7 +68,7 @@ lock_PyThread_acquire_lock(lockobject *self, PyObject *args)
PyDoc_STRVAR(acquire_doc,
"acquire([wait]) -> None or bool\n\
(PyThread_acquire_lock() is an obsolete synonym)\n\
(acquire_lock() is an obsolete synonym)\n\
\n\
Lock the lock. Without argument, this blocks if the lock is already\n\
locked (even by the same thread), waiting for another thread to release\n\
@ -94,7 +94,7 @@ lock_PyThread_release_lock(lockobject *self)
PyDoc_STRVAR(release_doc,
"release()\n\
(PyThread_release_lock() is an obsolete synonym)\n\
(release_lock() is an obsolete synonym)\n\
\n\
Release the lock, allowing another thread that is blocked waiting for\n\
the lock to acquire the lock. The lock must be in the locked state,\n\
@ -123,29 +123,6 @@ lock_context(lockobject *self)
return (PyObject *)self;
}
PyDoc_STRVAR(lock_exit_doc,
"__exit__(type, value, tb)\n\
\n\
Releases the lock; then re-raises the exception if type is not None.");
static PyObject *
lock_exit(lockobject *self, PyObject *args)
{
PyObject *type, *value, *tb, *result;
if (!PyArg_ParseTuple(args, "OOO:__exit__", &type, &value, &tb))
return NULL;
result = lock_PyThread_release_lock(self);
if (result != NULL && type != Py_None) {
Py_DECREF(result);
result = NULL;
Py_INCREF(type);
Py_INCREF(value);
Py_INCREF(tb);
PyErr_Restore(type, value, tb);
}
return result;
}
static PyMethodDef lock_methods[] = {
{"acquire_lock", (PyCFunction)lock_PyThread_acquire_lock,
METH_VARARGS, acquire_doc},
@ -163,8 +140,8 @@ static PyMethodDef lock_methods[] = {
METH_NOARGS, PyDoc_STR("__context__() -> self.")},
{"__enter__", (PyCFunction)lock_PyThread_acquire_lock,
METH_VARARGS, acquire_doc},
{"__exit__", (PyCFunction)lock_exit,
METH_VARARGS, lock_exit_doc},
{"__exit__", (PyCFunction)lock_PyThread_release_lock,
METH_VARARGS, release_doc},
{NULL, NULL} /* sentinel */
};

View File

@ -1617,24 +1617,6 @@ file_self(PyFileObject *f)
return (PyObject *)f;
}
static PyObject *
file_exit(PyFileObject *f, PyObject *args)
{
PyObject *type, *value, *tb, *result;
if (!PyArg_ParseTuple(args, "OOO:__exit__", &type, &value, &tb))
return NULL;
result = file_close(f);
if (result != NULL && type != Py_None) {
Py_DECREF(result);
result = NULL;
Py_INCREF(type);
Py_INCREF(value);
Py_INCREF(tb);
PyErr_Restore(type, value, tb);
}
return result;
}
PyDoc_STRVAR(readline_doc,
"readline([size]) -> next line from the file, as a string.\n"
"\n"
@ -1725,13 +1707,6 @@ PyDoc_STRVAR(context_doc,
PyDoc_STRVAR(enter_doc,
"__enter__() -> self.");
PyDoc_STRVAR(exit_doc,
"__exit__(type, value, traceback).\n\
\n\
Closes the file; then re-raises the exception if type is not None.\n\
If no exception is re-raised, the return value is the same as for close().\n\
");
static PyMethodDef file_methods[] = {
{"readline", (PyCFunction)file_readline, METH_VARARGS, readline_doc},
{"read", (PyCFunction)file_read, METH_VARARGS, read_doc},
@ -1751,7 +1726,7 @@ static PyMethodDef file_methods[] = {
{"isatty", (PyCFunction)file_isatty, METH_NOARGS, isatty_doc},
{"__context__", (PyCFunction)file_self, METH_NOARGS, context_doc},
{"__enter__", (PyCFunction)file_self, METH_NOARGS, enter_doc},
{"__exit__", (PyCFunction)file_exit, METH_VARARGS, exit_doc},
{"__exit__", (PyCFunction)file_close, METH_VARARGS, close_doc},
{NULL, NULL} /* sentinel */
};

View File

@ -2189,48 +2189,51 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
Below that are 1-3 values indicating how/why
we entered the finally clause:
- SECOND = None
- (SECOND, THIRD) = (WHY_RETURN or WHY_CONTINUE), retval
- (SECOND, THIRD) = (WHY_{RETURN,CONTINUE}), retval
- SECOND = WHY_*; no retval below it
- (SECOND, THIRD, FOURTH) = exc_info()
In the last case, we must call
TOP(SECOND, THIRD, FOURTH)
otherwise we must call
TOP(None, None, None)
but we must preserve the stack entries below TOP.
The code here just sets the stack up for the call;
separate CALL_FUNCTION(3) and POP_TOP opcodes are
emitted by the compiler.
In addition, if the stack represents an exception,
we "zap" this information; __exit__() should
re-raise the exception if it wants to, and if
__exit__() returns normally, END_FINALLY should
*not* re-raise the exception. (But non-local
gotos should still be resumed.)
*and* the function call returns a 'true' value, we
"zap" this information, to prevent END_FINALLY from
re-raising the exception. (But non-local gotos
should still be resumed.)
*/
x = TOP();
u = SECOND();
if (PyInt_Check(u) || u == Py_None) {
u = v = w = Py_None;
Py_INCREF(u);
Py_INCREF(v);
Py_INCREF(w);
}
else {
v = THIRD();
w = FOURTH();
/* Zap the exception from the stack,
to fool END_FINALLY. */
STACKADJ(-2);
SET_TOP(x);
Py_INCREF(Py_None);
SET_SECOND(Py_None);
}
STACKADJ(3);
SET_THIRD(u);
SET_SECOND(v);
SET_TOP(w);
/* XXX Not the fastest way to call it... */
x = PyObject_CallFunctionObjArgs(x, u, v, w, NULL);
if (x == NULL)
break; /* Go to error exit */
if (u != Py_None && PyObject_IsTrue(x)) {
/* There was an exception and a true return */
Py_DECREF(x);
x = TOP(); /* Again */
STACKADJ(-3);
Py_INCREF(Py_None);
SET_TOP(Py_None);
Py_DECREF(x);
Py_DECREF(u);
Py_DECREF(v);
Py_DECREF(w);
} else {
/* Let END_FINALLY do its thing */
Py_DECREF(x);
x = POP();
Py_DECREF(x);
}
break;
}

View File

@ -1382,7 +1382,7 @@ opcode_stack_effect(int opcode, int oparg)
case BREAK_LOOP:
return 0;
case WITH_CLEANUP:
return 3;
return -1; /* XXX Sometimes more */
case LOAD_LOCALS:
return 1;
case RETURN_VALUE:
@ -3472,8 +3472,6 @@ compiler_with(struct compiler *c, stmt_ty s)
!compiler_nameop(c, tmpexit, Del))
return 0;
ADDOP(c, WITH_CLEANUP);
ADDOP_I(c, CALL_FUNCTION, 3);
ADDOP(c, POP_TOP);
/* Finally block ends. */
ADDOP(c, END_FINALLY);

View File

@ -55,6 +55,7 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *);
Python 2.5a0: 62071
Python 2.5a0: 62081 (ast-branch)
Python 2.5a0: 62091 (with)
Python 2.5a0: 62092 (changed WITH_CLEANUP opcode)
.
*/
#define MAGIC (62092 | ((long)'\r'<<16) | ((long)'\n'<<24))