in http://codereview.appspot.com/53094 and accepted by Guido.
The construct is transformed into multiple With AST nodes so that
there should be no problems with the semantics.
patterns to be encapsulated for convenient reuse.
.. productionlist::
- with_stmt: "with" `expression` ["as" `target`] ":" `suite`
+ with_stmt: "with" with_item ("," with_item)* ":" `suite`
+ with_item: `expression` ["as" `target`]
-The execution of the :keyword:`with` statement proceeds as follows:
+The execution of the :keyword:`with` statement with one "item" proceeds as follows:
#. The context expression is evaluated to obtain a context manager.
from :meth:`__exit__` is ignored, and execution proceeds at the normal location
for the kind of exit that was taken.
+With more than one item, the context managers are processed as if multiple
+:keyword:`with` statements were nested::
+
+ with A() as a, B() as b:
+ suite
+
+is equivalent to ::
+
+ with A() as a:
+ with B() as b:
+ suite
+
.. note::
In Python 2.5, the :keyword:`with` statement is only allowed when the
``with_statement`` feature has been enabled. It is always enabled in
Python 2.6.
+.. versionchanged:: 2.7
+ Support for multiple context expressions.
+
.. seealso::
:pep:`0343` - The "with" statement
['else' ':' suite]
['finally' ':' suite] |
'finally' ':' suite))
-with_stmt: 'with' test [ with_var ] ':' suite
-with_var: 'as' expr
+with_stmt: 'with' with_item (',' with_item)* ':' suite
+with_item: test ['as' expr]
# NB compile.c makes sure that the default except clause is last
except_clause: 'except' [test [('as' | ',') test]]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
#define for_stmt 295
#define try_stmt 296
#define with_stmt 297
-#define with_var 298
+#define with_item 298
#define except_clause 299
#define suite 300
#define testlist_safe 301
return try_except
def com_with(self, nodelist):
- # with_stmt: 'with' expr [with_var] ':' suite
- expr = self.com_node(nodelist[1])
+ # with_stmt: 'with' with_item (',' with_item)* ':' suite
body = self.com_node(nodelist[-1])
- if nodelist[2][0] == token.COLON:
- var = None
+ for i in range(len(nodelist) - 3, 0, -2):
+ ret = self.com_with_item(nodelist[i], body, nodelist[0][2])
+ if i == 1:
+ return ret
+ body = ret
+
+ def com_with_item(self, nodelist, body, lineno):
+ # with_item: test ['as' expr]
+ if len(nodelist) == 4:
+ var = self.com_assign(nodelist[3], OP_ASSIGN)
else:
- var = self.com_assign(nodelist[2][2], OP_ASSIGN)
- return With(expr, var, body, lineno=nodelist[0][2])
-
- def com_with_var(self, nodelist):
- # with_var: 'as' expr
- return self.com_node(nodelist[1])
+ var = None
+ expr = self.com_node(nodelist[1])
+ return With(expr, var, body, lineno=lineno)
def com_augassign_op(self, node):
assert node[0] == symbol.augassign
exec c in dct
self.assertEquals(dct.get('result'), 1)
+ def testWithMult(self):
+ events = []
+ class Ctx:
+ def __init__(self, n):
+ self.n = n
+ def __enter__(self):
+ events.append(self.n)
+ def __exit__(self, *args):
+ pass
+ c = compiler.compile('from __future__ import with_statement\n'
+ 'def f():\n'
+ ' with Ctx(1) as tc, Ctx(2) as tc2:\n'
+ ' return 1\n'
+ 'result = f()',
+ '<string>',
+ 'exec' )
+ dct = {'Ctx': Ctx}
+ exec c in dct
+ self.assertEquals(dct.get('result'), 1)
+ self.assertEquals(events, [1, 2])
+
def testGlobal(self):
code = compiler.compile('global x\nx=1', '<string>', 'exec')
d1 = {'__builtins__': {}}
def test_with(self):
self.check_suite("with open('x'): pass\n")
self.check_suite("with open('x') as f: pass\n")
+ self.check_suite("with open('x') as f, open('y') as g: pass\n")
def test_try_stmt(self):
self.check_suite("try: pass\nexcept: pass\n")
self.fail("ZeroDivisionError should have been raised")
+class NestedWith(unittest.TestCase):
+
+ class Dummy(object):
+ def __init__(self, value=None, gobble=False):
+ if value is None:
+ value = self
+ self.value = value
+ self.gobble = gobble
+ self.enter_called = False
+ self.exit_called = False
+
+ def __enter__(self):
+ self.enter_called = True
+ return self.value
+
+ def __exit__(self, *exc_info):
+ self.exit_called = True
+ self.exc_info = exc_info
+ if self.gobble:
+ return True
+
+ class CtorRaises(object):
+ def __init__(self): raise RuntimeError()
+
+ class EnterRaises(object):
+ def __enter__(self): raise RuntimeError()
+ def __exit__(self, *exc_info): pass
+
+ class ExitRaises(object):
+ def __enter__(self): pass
+ def __exit__(self, *exc_info): raise RuntimeError()
+
+ def testNoExceptions(self):
+ with self.Dummy() as a, self.Dummy() as b:
+ self.assertTrue(a.enter_called)
+ self.assertTrue(b.enter_called)
+ self.assertTrue(a.exit_called)
+ self.assertTrue(b.exit_called)
+
+ def testExceptionInExprList(self):
+ try:
+ with self.Dummy() as a, self.CtorRaises():
+ pass
+ except:
+ pass
+ self.assertTrue(a.enter_called)
+ self.assertTrue(a.exit_called)
+
+ def testExceptionInEnter(self):
+ try:
+ with self.Dummy() as a, self.EnterRaises():
+ self.fail('body of bad with executed')
+ except RuntimeError:
+ pass
+ else:
+ self.fail('RuntimeError not reraised')
+ self.assertTrue(a.enter_called)
+ self.assertTrue(a.exit_called)
+
+ def testExceptionInExit(self):
+ body_executed = False
+ with self.Dummy(gobble=True) as a, self.ExitRaises():
+ body_executed = True
+ self.assertTrue(a.enter_called)
+ self.assertTrue(a.exit_called)
+ self.assertNotEqual(a.exc_info[0], None)
+
+ def testEnterReturnsTuple(self):
+ with self.Dummy(value=(1,2)) as (a1, a2), \
+ self.Dummy(value=(10, 20)) as (b1, b2):
+ self.assertEquals(1, a1)
+ self.assertEquals(2, a2)
+ self.assertEquals(10, b1)
+ self.assertEquals(20, b2)
+
def test_main():
run_unittest(FailureTestCase, NonexceptionalTestCase,
NestedNonexceptionalTestCase, ExceptionalTestCase,
NonLocalFlowControlTestCase,
AssignmentTargetTestCase,
- ExitSwallowsExceptionTestCase)
+ ExitSwallowsExceptionTestCase,
+ NestedWith)
if __name__ == '__main__':
Core and Builtins
-----------------
+- Added support for multiple context managers in the same with statement.
+
- 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.
return ok;
}
-/* with_var
-with_var: 'as' expr
+/* with_item:
+ * test ['as' expr]
*/
static int
-validate_with_var(node *tree)
+validate_with_item(node *tree)
{
int nch = NCH(tree);
- int ok = (validate_ntype(tree, with_var)
- && (nch == 2)
- && validate_name(CHILD(tree, 0), "as")
- && validate_expr(CHILD(tree, 1)));
- return ok;
+ int ok = (validate_ntype(tree, with_item)
+ && (nch == 1 || nch == 3)
+ && validate_test(CHILD(tree, 0)));
+ if (ok && nch == 3)
+ ok = (validate_name(CHILD(tree, 1), "as")
+ && validate_expr(CHILD(tree, 2)));
+ return ok;
}
-/* with_stmt
- * 0 1 2 -2 -1
-with_stmt: 'with' test [ with_var ] ':' suite
+/* with_stmt:
+ * 0 1 ... -2 -1
+ * 'with' with_item (',' with_item)* ':' suite
*/
static int
validate_with_stmt(node *tree)
{
+ int i;
int nch = NCH(tree);
int ok = (validate_ntype(tree, with_stmt)
- && ((nch == 4) || (nch == 5))
+ && (nch % 2 == 0)
&& validate_name(CHILD(tree, 0), "with")
- && validate_test(CHILD(tree, 1))
- && (nch == 4 || validate_with_var(CHILD(tree, 2)))
&& validate_colon(RCHILD(tree, -2))
&& validate_suite(RCHILD(tree, -1)));
- return ok;
+ for (i = 1; ok && i < nch - 2; i += 2)
+ ok = validate_with_item(CHILD(tree, i));
+ return ok;
}
/* funcdef:
return TryFinally(body, finally, LINENO(n), n->n_col_offset, c->c_arena);
}
-static expr_ty
-ast_for_with_var(struct compiling *c, const node *n)
-{
- REQ(n, with_var);
- return ast_for_expr(c, CHILD(n, 1));
-}
-
-/* with_stmt: 'with' test [ with_var ] ':' suite */
+/* with_item: test ['as' expr] */
static stmt_ty
-ast_for_with_stmt(struct compiling *c, const node *n)
+ast_for_with_item(struct compiling *c, const node *n, asdl_seq *content)
{
expr_ty context_expr, optional_vars = NULL;
- int suite_index = 3; /* skip 'with', test, and ':' */
- asdl_seq *suite_seq;
- assert(TYPE(n) == with_stmt);
- context_expr = ast_for_expr(c, CHILD(n, 1));
+ REQ(n, with_item);
+ context_expr = ast_for_expr(c, CHILD(n, 0));
if (!context_expr)
return NULL;
- if (TYPE(CHILD(n, 2)) == with_var) {
- optional_vars = ast_for_with_var(c, CHILD(n, 2));
+ if (NCH(n) == 3) {
+ optional_vars = ast_for_expr(c, CHILD(n, 2));
if (!optional_vars) {
return NULL;
if (!set_context(c, optional_vars, Store, n)) {
return NULL;
}
- suite_index = 4;
}
- suite_seq = ast_for_suite(c, CHILD(n, suite_index));
- if (!suite_seq) {
+ return With(context_expr, optional_vars, content, LINENO(n),
+ n->n_col_offset, c->c_arena);
+}
+
+/* with_stmt: 'with' with_item (',' with_item)* ':' suite */
+static stmt_ty
+ast_for_with_stmt(struct compiling *c, const node *n)
+{
+ int i;
+ stmt_ty ret;
+ asdl_seq *inner;
+
+ REQ(n, with_stmt);
+
+ /* process the with items inside-out */
+ i = NCH(n) - 1;
+ /* the suite of the innermost with item is the suite of the with stmt */
+ inner = ast_for_suite(c, CHILD(n, i));
+ if (!inner)
return NULL;
+
+ for (;;) {
+ i -= 2;
+ ret = ast_for_with_item(c, CHILD(n, i), inner);
+ if (!ret)
+ return NULL;
+ /* was this the last item? */
+ if (i == 1)
+ break;
+ /* if not, wrap the result so far in a new sequence */
+ inner = asdl_seq_new(1, c->c_arena);
+ if (!inner)
+ return NULL;
+ asdl_seq_SET(inner, 0, ret);
}
- return With(context_expr, optional_vars, suite_seq, LINENO(n),
- n->n_col_offset, c->c_arena);
+
+ return ret;
}
static stmt_ty
{100, 1},
};
static arc arcs_41_1[1] = {
- {28, 2},
+ {101, 2},
};
static arc arcs_41_2[2] = {
- {101, 3},
- {23, 4},
+ {29, 1},
+ {23, 3},
};
static arc arcs_41_3[1] = {
- {23, 4},
+ {24, 4},
};
static arc arcs_41_4[1] = {
- {24, 5},
-};
-static arc arcs_41_5[1] = {
- {0, 5},
+ {0, 4},
};
-static state states_41[6] = {
+static state states_41[5] = {
{1, arcs_41_0},
{1, arcs_41_1},
{2, arcs_41_2},
{1, arcs_41_3},
{1, arcs_41_4},
- {1, arcs_41_5},
};
static arc arcs_42_0[1] = {
- {80, 1},
+ {28, 1},
};
-static arc arcs_42_1[1] = {
- {84, 2},
+static arc arcs_42_1[2] = {
+ {80, 2},
+ {0, 1},
};
static arc arcs_42_2[1] = {
- {0, 2},
+ {84, 3},
+};
+static arc arcs_42_3[1] = {
+ {0, 3},
};
-static state states_42[3] = {
+static state states_42[4] = {
{1, arcs_42_0},
- {1, arcs_42_1},
+ {2, arcs_42_1},
{1, arcs_42_2},
+ {1, arcs_42_3},
};
static arc arcs_43_0[1] = {
{102, 1},
"\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000\000\000\000\000\000"},
{296, "try_stmt", 0, 13, states_40,
"\000\000\000\000\000\000\000\000\000\000\000\000\002\000\000\000\000\000\000\000\000\000"},
- {297, "with_stmt", 0, 6, states_41,
+ {297, "with_stmt", 0, 5, states_41,
"\000\000\000\000\000\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\000\000"},
- {298, "with_var", 0, 3, states_42,
- "\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000"},
+ {298, "with_item", 0, 4, states_42,
+ "\000\040\040\000\000\000\000\000\000\000\000\000\000\040\010\000\200\041\044\015\000\000"},
{299, "except_clause", 0, 5, states_43,
"\000\000\000\000\000\000\000\000\000\000\000\000\100\000\000\000\000\000\000\000\000\000"},
{300, "suite", 0, 5, states_44,