Index: Python/ceval.c =================================================================== --- Python/ceval.c (revision 61190) +++ Python/ceval.c (working copy) @@ -2254,18 +2254,21 @@ case WITH_CLEANUP: { - /* TOP is the context.__exit__ bound method. - Below that are 1-3 values indicating how/why - we entered the finally clause: - - SECOND = None - - (SECOND, THIRD) = (WHY_{RETURN,CONTINUE}), retval - - SECOND = WHY_*; no retval below it - - (SECOND, THIRD, FOURTH) = exc_info() + /* At the top of the stack are 1-3 values indicating + how/why we entered the finally clause: + - TOP = None + - (TOP, SECOND) = (WHY_{RETURN,CONTINUE}), retval + - TOP = WHY_*; no retval below it + - (TOP, SECOND, THIRD) = exc_info() + Below them is EXIT, the context.__exit__ bound method. In the last case, we must call - TOP(SECOND, THIRD, FOURTH) + EXIT(TOP, SECOND, THIRD) otherwise we must call - TOP(None, None, None) + EXIT(None, None, None) + In all cases, we remove EXIT from the stack, leaving + the rest in the same order. + In addition, if the stack represents an exception, *and* the function call returns a 'true' value, we "zap" this information, to prevent END_FINALLY from @@ -2273,36 +2276,59 @@ should still be resumed.) */ - x = TOP(); - u = SECOND(); - if (PyInt_Check(u) || u == Py_None) { + PyObject *exit_func; + + u = POP(); + if (u == Py_None) { + exit_func = TOP(); + SET_TOP(u); + v = w = Py_None; + } + else if (PyInt_Check(u)) { + switch(PyInt_AS_LONG(u)) { + case WHY_RETURN: + case WHY_CONTINUE: + /* Retval in TOP. */ + exit_func = SECOND(); + SET_SECOND(TOP()); + SET_TOP(u); + break; + default: + exit_func = TOP(); + SET_TOP(u); + break; + } u = v = w = Py_None; } else { - v = THIRD(); - w = FOURTH(); + v = TOP(); + w = SECOND(); + exit_func = THIRD(); + SET_TOP(u); + SET_SECOND(v); + SET_THIRD(w); } /* XXX Not the fastest way to call it... */ - x = PyObject_CallFunctionObjArgs(x, u, v, w, NULL); - if (x == NULL) + x = PyObject_CallFunctionObjArgs(exit_func, u, v, w, + NULL); + if (x == NULL) { + Py_DECREF(exit_func); 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); + STACKADJ(-2); 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); + /* The stack was rearranged to remove EXIT + above. Let END_FINALLY do its thing */ } + Py_DECREF(x); + Py_DECREF(exit_func); PREDICT(END_FINALLY); break; } Index: Python/import.c =================================================================== --- Python/import.c (revision 61190) +++ Python/import.c (working copy) @@ -72,9 +72,10 @@ storing constants that should have been removed) Python 2.5c2: 62131 (fix wrong code: for x, in ... in listcomp/genexp) Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode) + Python 2.6a1: 62161 (WITH_CLEANUP optimization) . */ -#define MAGIC (62151 | ((long)'\r'<<16) | ((long)'\n'<<24)) +#define MAGIC (62161 | ((long)'\r'<<16) | ((long)'\n'<<24)) /* Magic word as global; note that _PyImport_Init() can change the value of this global to accommodate for alterations of how the Index: Python/compile.c =================================================================== --- Python/compile.c (revision 61190) +++ Python/compile.c (working copy) @@ -2842,7 +2842,7 @@ { static identifier enter_attr, exit_attr; basicblock *block, *finally; - identifier tmpexit, tmpvalue = NULL; + identifier tmpvalue = NULL; assert(s->kind == With_kind); @@ -2862,12 +2862,6 @@ if (!block || !finally) return 0; - /* Create a temporary variable to hold context.__exit__ */ - tmpexit = compiler_new_tmpname(c); - if (tmpexit == NULL) - return 0; - PyArena_AddPyObject(c->c_arena, tmpexit); - if (s->v.With.optional_vars) { /* Create a temporary variable to hold context.__enter__(). We need to do this rather than preserving it on the stack @@ -2887,11 +2881,10 @@ /* Evaluate EXPR */ VISIT(c, expr, s->v.With.context_expr); - /* Squirrel away context.__exit__ */ + /* Squirrel away context.__exit__ by stuffing it under context */ ADDOP(c, DUP_TOP); ADDOP_O(c, LOAD_ATTR, exit_attr, names); - if (!compiler_nameop(c, tmpexit, Store)) - return 0; + ADDOP(c, ROT_TWO); /* Call context.__enter__() */ ADDOP_O(c, LOAD_ATTR, enter_attr, names); @@ -2935,10 +2928,9 @@ if (!compiler_push_fblock(c, FINALLY_END, finally)) return 0; - /* Finally block starts; push tmpexit and issue our magic opcode. */ - if (!compiler_nameop(c, tmpexit, Load) || - !compiler_nameop(c, tmpexit, Del)) - return 0; + /* Finally block starts; context.__exit__ is on the stack under + the exception or return information. Just issue our magic + opcode. */ ADDOP(c, WITH_CLEANUP); /* Finally block ends. */ Index: Doc/library/dis.rst =================================================================== --- Doc/library/dis.rst (revision 61190) +++ Doc/library/dis.rst (working copy) @@ -519,22 +519,25 @@ .. opcode:: WITH_CLEANUP () - Cleans up the stack when a :keyword:`with` statement block exits. TOS is the - context manager's :meth:`__exit__` bound method. Below that are 1--3 values - indicating how/why the finally clause was entered: + Cleans up the stack when a :keyword:`with` statement block exits. On top of + the stack are 1--3 values indicating how/why the finally clause was entered: - * SECOND = ``None`` - * (SECOND, THIRD) = (``WHY_{RETURN,CONTINUE}``), retval - * SECOND = ``WHY_*``; no retval below it - * (SECOND, THIRD, FOURTH) = exc_info() + * TOP = ``None`` + * (TOP, SECOND) = (``WHY_{RETURN,CONTINUE}``), retval + * TOP = ``WHY_*``; no retval below it + * (TOP, SECOND, THIRD) = exc_info() - In the last case, ``TOS(SECOND, THIRD, FOURTH)`` is called, otherwise - ``TOS(None, None, None)``. + Under them is EXIT, the context manager's :meth:`__exit__` bound method. - In addition, if the stack represents an exception, *and* the function call - returns a 'true' value, this information is "zapped", to prevent ``END_FINALLY`` - from re-raising the exception. (But non-local gotos should still be resumed.) + In the last case, ``EXIT(TOP, SECOND, THIRD)`` is called, otherwise + ``EXIT(None, None, None)``. + EXIT is removed from the stack, leaving the values above it in the same + order. In addition, if the stack represents an exception, *and* the function + call returns a 'true' value, this information is "zapped", to prevent + ``END_FINALLY`` from re-raising the exception. (But non-local gotos should + still be resumed.) + .. XXX explain the WHY stuff! Index: Lib/compiler/pycodegen.py =================================================================== --- Lib/compiler/pycodegen.py (revision 61190) +++ Lib/compiler/pycodegen.py (working copy) @@ -822,14 +822,13 @@ def visitWith(self, node): body = self.newBlock() final = self.newBlock() - exitvar = "$exit%d" % self.__with_count valuevar = "$value%d" % self.__with_count self.__with_count += 1 self.set_lineno(node) self.visit(node.expr) self.emit('DUP_TOP') self.emit('LOAD_ATTR', '__exit__') - self._implicitNameOp('STORE', exitvar) + self.emit('ROT_TWO') self.emit('LOAD_ATTR', '__enter__') self.emit('CALL_FUNCTION', 0) if node.vars is None: @@ -849,8 +848,6 @@ self.emit('LOAD_CONST', None) self.nextBlock(final) self.setups.push((END_FINALLY, final)) - self._implicitNameOp('LOAD', exitvar) - self._implicitNameOp('DELETE', exitvar) self.emit('WITH_CLEANUP') self.emit('END_FINALLY') self.setups.pop()