From f669436189dd44a841caa9ab1ad97a3f7662bf58 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 10 Mar 2006 02:28:35 +0000 Subject: 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. --- Lib/compiler/pyassem.py | 2 +- Lib/compiler/pycodegen.py | 2 -- Lib/contextlib.py | 11 +++++------ Lib/decimal.py | 2 -- Lib/test/test_with.py | 32 ++++++++++++++++++++++---------- Lib/threading.py | 6 ------ Modules/threadmodule.c | 31 ++++--------------------------- Objects/fileobject.c | 27 +-------------------------- Python/ceval.c | 47 +++++++++++++++++++++++++---------------------- Python/compile.c | 4 +--- Python/import.c | 1 + 11 files changed, 60 insertions(+), 105 deletions(-) diff --git a/Lib/compiler/pyassem.py b/Lib/compiler/pyassem.py index b59661f..82ff396 100644 --- a/Lib/compiler/pyassem.py +++ b/Lib/compiler/pyassem.py @@ -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 = [ diff --git a/Lib/compiler/pycodegen.py b/Lib/compiler/pycodegen.py index a1236de..2b3a24f 100644 --- a/Lib/compiler/pycodegen.py +++ b/Lib/compiler/pycodegen.py @@ -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 diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 33c302d..0a5d608 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -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 diff --git a/Lib/decimal.py b/Lib/decimal.py index 49f8115..967f101 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -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. diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py index 36035e3..4854436 100644 --- a/Lib/test/test_with.py +++ b/Lib/test/test_with.py @@ -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, diff --git a/Lib/threading.py b/Lib/threading.py index 5b485d5..cc1adce 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -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 "" % (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): diff --git a/Modules/threadmodule.c b/Modules/threadmodule.c index f15cacb..9a6c5d8 100644 --- a/Modules/threadmodule.c +++ b/Modules/threadmodule.c @@ -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 */ }; diff --git a/Objects/fileobject.c b/Objects/fileobject.c index b39a10f..57a9e9d 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -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 */ }; diff --git a/Python/ceval.c b/Python/ceval.c index e7fb875..de2b35b 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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); + } + /* 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_SECOND(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); } - STACKADJ(3); - SET_THIRD(u); - SET_SECOND(v); - SET_TOP(w); break; } diff --git a/Python/compile.c b/Python/compile.c index c07b6d3..e3b3df8 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -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); diff --git a/Python/import.c b/Python/import.c index f214ed5..73051a2 100644 --- a/Python/import.c +++ b/Python/import.c @@ -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)) -- cgit v0.12