]> granicus.if.org Git - python/commitdiff
add a SETUP_WITH opcode
authorBenjamin Peterson <benjamin@python.org>
Mon, 25 May 2009 13:13:44 +0000 (13:13 +0000)
committerBenjamin Peterson <benjamin@python.org>
Mon, 25 May 2009 13:13:44 +0000 (13:13 +0000)
It speeds up the with statement and correctly looks up the special
methods involved.

Doc/library/dis.rst
Doc/reference/compound_stmts.rst
Include/opcode.h
Lib/opcode.py
Lib/test/test_descr.py
Misc/NEWS
Python/ceval.c
Python/compile.c
Python/import.c

index 6b7ed1eed32c222582dabaac4a0385bbeed5c4bc..9bd65dd426188749090d47475e6053f47f497862 100644 (file)
@@ -532,6 +532,18 @@ Miscellaneous opcodes.
    the names of the base classes, and TOS2 the class name.
 
 
+.. opcode:: SETUP_WITH (delta)
+
+   This opcode performs several operations before a with block starts.  First,
+   it loads :meth:`~object.__exit__` from the context manager and pushes it onto
+   the stack for later use by :opcode:`WITH_CLEANUP`.  Then,
+   :meth:`~object.__enter__` is called, and a finally block pointing to *delta*
+   is pushed.  Finally, the result of calling the enter method is pushed onto
+   the stack.  The next opcode will either ignore it (:opcode:`POP_TOP`), or
+   store it in (a) variable(s) (:opcode:`STORE_FAST`, :opcode:`STORE_NAME`, or
+   :opcode:`UNPACK_SEQUENCE`).
+
+
 .. opcode:: WITH_CLEANUP ()
 
    Cleans up the stack when a :keyword:`with` statement block exits.  On top of
index 3c716b286f000ccec3b81efa3a6324aef0c3cc95..afb7ebc6d50151e3f6fc9a8552e836177c41820f 100644 (file)
@@ -339,6 +339,8 @@ The execution of the :keyword:`with` statement proceeds as follows:
 
 #. The context expression is evaluated to obtain a context manager.
 
+#. The context manager's :meth:`__exit__` is loaded for later use.
+
 #. The context manager's :meth:`__enter__` method is invoked.
 
 #. If a target was included in the :keyword:`with` statement, the return value
@@ -349,7 +351,7 @@ The execution of the :keyword:`with` statement proceeds as follows:
       The :keyword:`with` statement guarantees that if the :meth:`__enter__` method
       returns without an error, then :meth:`__exit__` will always be called. Thus, if
       an error occurs during the assignment to the target list, it will be treated the
-      same as an error occurring within the suite would be. See step 5 below.
+      same as an error occurring within the suite would be. See step 6 below.
 
 #. The suite is executed.
 
index 36a35dc2fea4c50a4f335f28201fb3925f8f5f11..9739782ce2ca8a68650e6c5fc14a6647710aac61 100644 (file)
@@ -141,8 +141,10 @@ extern "C" {
 #define CALL_FUNCTION_KW           141 /* #args + (#kwargs<<8) */
 #define CALL_FUNCTION_VAR_KW       142 /* #args + (#kwargs<<8) */
 
+#define SETUP_WITH 143
+
 /* Support for opargs more than 16 bits long */
-#define EXTENDED_ARG  143
+#define EXTENDED_ARG  145
 
 
 enum cmp_op {PyCmp_LT=Py_LT, PyCmp_LE=Py_LE, PyCmp_EQ=Py_EQ, PyCmp_NE=Py_NE, PyCmp_GT=Py_GT, PyCmp_GE=Py_GE,
index 24d5dcc3feb7b7cddbe3d13f625d632673191630..7c3962d83b1afc41d081760503a51d5306cedcb2 100644 (file)
@@ -181,7 +181,10 @@ hasfree.append(137)
 def_op('CALL_FUNCTION_VAR', 140)     # #args + (#kwargs << 8)
 def_op('CALL_FUNCTION_KW', 141)      # #args + (#kwargs << 8)
 def_op('CALL_FUNCTION_VAR_KW', 142)  # #args + (#kwargs << 8)
-def_op('EXTENDED_ARG', 143)
-EXTENDED_ARG = 143
+
+jrel_op('SETUP_WITH', 143)
+
+def_op('EXTENDED_ARG', 145)
+EXTENDED_ARG = 145
 
 del def_op, name_op, jrel_op, jabs_op
index c7989b11362d48d024084ae4a7e54909bdb88cbb..627f8aa0977fbc8ec724287dfff35aa4e0166770 100644 (file)
@@ -1689,6 +1689,7 @@ order (MRO) for bases """
             return isinstance(int, obj)
         def do_issubclass(obj):
             return issubclass(int, obj)
+        def swallow(*args): pass
 
         # It would be nice to have every special method tested here, but I'm
         # only listing the ones I can remember outside of typeobject.c, since it
@@ -1702,11 +1703,8 @@ order (MRO) for bases """
             ("__instancecheck__", do_isinstance, return_true, set(), {}),
             ("__subclasscheck__", do_issubclass, return_true,
              set(("__bases__",)), {}),
-            # These two fail because the compiler generates LOAD_ATTR to look
-            # them up.  We'd have to add a new opcode to fix this, and it's
-            # probably not worth it.
-            # ("__enter__", run_context, iden),
-            # ("__exit__", run_context, iden),
+            ("__enter__", run_context, iden, set(), {"__exit__" : swallow}),
+            ("__exit__", run_context, swallow, set(), {"__enter__" : iden}),
             ]
 
         class Checker(object):
index 5526d9c098a290cc2c75b534315920c4f18200c3..a6b5fce442005790ded8b7ed2b6823b2c1f6f110 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 1
 Core and Builtins
 -----------------
 
+- Issue #6101: A new opcode, SETUP_WITH, has been added to speed up the with
+  statement and correctly lookup the __enter__ and __exit__ special methods.
+
 - Issue #5829: complex("1e500") no longer raises OverflowError.  This
   makes it consistent with float("1e500") and interpretation of real
   and imaginary literals.
index 4f0877b9fa67828445e3b0592d4060b86f5a5b8d..92021e651a91a75922742bc661ca92c30151c6e3 100644 (file)
@@ -128,6 +128,7 @@ static void format_exc_check_arg(PyObject *, char *, PyObject *);
 static PyObject * string_concatenate(PyObject *, PyObject *,
                                    PyFrameObject *, unsigned char *);
 static PyObject * kwd_as_string(PyObject *);
+static PyObject * special_lookup(PyObject *, char *, PyObject **);
 
 #define NAME_ERROR_MSG \
        "name '%.200s' is not defined"
@@ -2467,6 +2468,33 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
                                           STACK_LEVEL());
                        continue;
 
+               case SETUP_WITH:
+                {
+                       static PyObject *exit, *enter;
+                       w = TOP();
+                       x = special_lookup(w, "__exit__", &exit);
+                       if (!x)
+                               break;
+                       SET_TOP(x);
+                       u = special_lookup(w, "__enter__", &enter);
+                       Py_DECREF(w);
+                       if (!u) {
+                               x = NULL;
+                               break;
+                       }
+                       x = PyObject_CallFunctionObjArgs(u, NULL);
+                       Py_DECREF(u);
+                       if (!x)
+                               break;
+                       /* Setup the finally block before pushing the result
+                          of __enter__ on the stack. */
+                       PyFrame_BlockSetup(f, SETUP_FINALLY, INSTR_OFFSET() + oparg,
+                                          STACK_LEVEL());
+
+                       PUSH(x);
+                       continue;
+               }
+
                case WITH_CLEANUP:
                {
                        /* At the top of the stack are 1-3 values indicating
@@ -3171,6 +3199,24 @@ fail: /* Jump here from prelude on failure */
 }
 
 
+static PyObject *
+special_lookup(PyObject *o, char *meth, PyObject **cache)
+{
+       PyObject *res;
+       if (PyInstance_Check(o)) {
+               if (!*cache)
+                       return PyObject_GetAttrString(o, meth);
+               else
+                       return PyObject_GetAttr(o, *cache);
+       }
+       res = _PyObject_LookupSpecial(o, meth, cache);
+       if (res == NULL && !PyErr_Occurred()) {
+               PyErr_SetObject(PyExc_AttributeError, *cache);
+               return NULL;
+       }
+       return res;
+}
+
 
 static PyObject *
 kwd_as_string(PyObject *kwd) {
index 8c8530670b6e80ddd70d822a65dca73e0343d677..e263d76342a7e6ec6d890fc1fd61e4f3f85a613c 100644 (file)
@@ -778,6 +778,8 @@ opcode_stack_effect(int opcode, int oparg)
                        return -1;
                case BREAK_LOOP:
                        return 0;
+               case SETUP_WITH:
+                       return 1;
                case WITH_CLEANUP:
                        return -1; /* XXX Sometimes more */
                case LOAD_LOCALS:
@@ -2821,80 +2823,31 @@ expr_constant(expr_ty e)
 static int
 compiler_with(struct compiler *c, stmt_ty s)
 {
-    static identifier enter_attr, exit_attr;
     basicblock *block, *finally;
-    identifier tmpvalue = NULL;
 
     assert(s->kind == With_kind);
 
-    if (!enter_attr) {
-       enter_attr = PyString_InternFromString("__enter__");
-       if (!enter_attr)
-           return 0;
-    }
-    if (!exit_attr) {
-       exit_attr = PyString_InternFromString("__exit__");
-       if (!exit_attr)
-           return 0;
-    }
-
     block = compiler_new_block(c);
     finally = compiler_new_block(c);
     if (!block || !finally)
        return 0;
 
-    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
-          because SETUP_FINALLY remembers the stack level.
-          We need to do the assignment *inside* the try/finally
-          so that context.__exit__() is called when the assignment
-          fails.  But we need to call context.__enter__() *before*
-          the try/finally so that if it fails we won't call
-          context.__exit__().
-       */
-       tmpvalue = compiler_new_tmpname(c);
-       if (tmpvalue == NULL)
-           return 0;
-       PyArena_AddPyObject(c->c_arena, tmpvalue);
-    }
-
     /* Evaluate EXPR */
     VISIT(c, expr, s->v.With.context_expr);
+    ADDOP_JREL(c, SETUP_WITH, finally);
 
-    /* Squirrel away context.__exit__ by stuffing it under context */
-    ADDOP(c, DUP_TOP);
-    ADDOP_O(c, LOAD_ATTR, exit_attr, names);
-    ADDOP(c, ROT_TWO);
-
-    /* Call context.__enter__() */
-    ADDOP_O(c, LOAD_ATTR, enter_attr, names);
-    ADDOP_I(c, CALL_FUNCTION, 0);
-
-    if (s->v.With.optional_vars) {
-       /* Store it in tmpvalue */
-       if (!compiler_nameop(c, tmpvalue, Store))
-           return 0;
-    }
-    else {
-       /* Discard result from context.__enter__() */
-       ADDOP(c, POP_TOP);
-    }
-
-    /* Start the try block */
-    ADDOP_JREL(c, SETUP_FINALLY, finally);
-
+    /* SETUP_WITH pushes a finally block. */
     compiler_use_next_block(c, block);
     if (!compiler_push_fblock(c, FINALLY_TRY, block)) {
        return 0;
     }
 
     if (s->v.With.optional_vars) {
-       /* Bind saved result of context.__enter__() to VAR */
-       if (!compiler_nameop(c, tmpvalue, Load) ||
-           !compiler_nameop(c, tmpvalue, Del))
-         return 0;
-       VISIT(c, expr, s->v.With.optional_vars);
+        VISIT(c, expr, s->v.With.optional_vars);
+    }
+    else {
+        /* Discard result from context.__enter__() */
+        ADDOP(c, POP_TOP);
     }
 
     /* BLOCK code */
index 5e42e5c9f30ffed6f7af4eb2aef4d7b486e9ed99..88aced06d102799a2ddf7a6d1a30eaa11a3880f8 100644 (file)
@@ -74,9 +74,10 @@ typedef unsigned short mode_t;
        Python 2.7a0: 62171 (optimize list comprehensions/change LIST_APPEND)
        Python 2.7a0: 62181 (optimize conditional branches:
                            introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE)
+       Python 2.7a0  62191 (introduce SETUP_WITH)
 .
 */
-#define MAGIC (62181 | ((long)'\r'<<16) | ((long)'\n'<<24))
+#define MAGIC (62191 | ((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