Allow multiple context managers in one with statement, as proposed

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.
This commit is contained in:
Georg Brandl 2009-05-25 21:02:56 +00:00
parent 04516611e7
commit 944f684ce6
11 changed files with 216 additions and 71 deletions

View file

@ -333,9 +333,10 @@ allows common :keyword:`try`...\ :keyword:`except`...\ :keyword:`finally` usage
patterns to be encapsulated for convenient reuse. patterns to be encapsulated for convenient reuse.
.. productionlist:: .. 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. #. The context expression is evaluated to obtain a context manager.
@ -369,12 +370,27 @@ The execution of the :keyword:`with` statement proceeds as follows:
from :meth:`__exit__` is ignored, and execution proceeds at the normal location from :meth:`__exit__` is ignored, and execution proceeds at the normal location
for the kind of exit that was taken. 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:: .. note::
In Python 2.5, the :keyword:`with` statement is only allowed when the In Python 2.5, the :keyword:`with` statement is only allowed when the
``with_statement`` feature has been enabled. It is always enabled in ``with_statement`` feature has been enabled. It is always enabled in
Python 2.6. Python 2.6.
.. versionchanged:: 2.7
Support for multiple context expressions.
.. seealso:: .. seealso::
:pep:`0343` - The "with" statement :pep:`0343` - The "with" statement

View file

@ -83,8 +83,8 @@ try_stmt: ('try' ':' suite
['else' ':' suite] ['else' ':' suite]
['finally' ':' suite] | ['finally' ':' suite] |
'finally' ':' suite)) 'finally' ':' suite))
with_stmt: 'with' test [ with_var ] ':' suite with_stmt: 'with' with_item (',' with_item)* ':' suite
with_var: 'as' expr with_item: test ['as' expr]
# NB compile.c makes sure that the default except clause is last # NB compile.c makes sure that the default except clause is last
except_clause: 'except' [test [('as' | ',') test]] except_clause: 'except' [test [('as' | ',') test]]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT

View file

@ -42,7 +42,7 @@
#define for_stmt 295 #define for_stmt 295
#define try_stmt 296 #define try_stmt 296
#define with_stmt 297 #define with_stmt 297
#define with_var 298 #define with_item 298
#define except_clause 299 #define except_clause 299
#define suite 300 #define suite 300
#define testlist_safe 301 #define testlist_safe 301

View file

@ -965,18 +965,22 @@ class Transformer:
return try_except return try_except
def com_with(self, nodelist): def com_with(self, nodelist):
# with_stmt: 'with' expr [with_var] ':' suite # with_stmt: 'with' with_item (',' with_item)* ':' suite
expr = self.com_node(nodelist[1])
body = self.com_node(nodelist[-1]) body = self.com_node(nodelist[-1])
if nodelist[2][0] == token.COLON: for i in range(len(nodelist) - 3, 0, -2):
var = None ret = self.com_with_item(nodelist[i], body, nodelist[0][2])
else: if i == 1:
var = self.com_assign(nodelist[2][2], OP_ASSIGN) return ret
return With(expr, var, body, lineno=nodelist[0][2]) body = ret
def com_with_var(self, nodelist): def com_with_item(self, nodelist, body, lineno):
# with_var: 'as' expr # with_item: test ['as' expr]
return self.com_node(nodelist[1]) if len(nodelist) == 4:
var = self.com_assign(nodelist[3], OP_ASSIGN)
else:
var = None
expr = self.com_node(nodelist[1])
return With(expr, var, body, lineno=lineno)
def com_augassign_op(self, node): def com_augassign_op(self, node):
assert node[0] == symbol.augassign assert node[0] == symbol.augassign

View file

@ -165,6 +165,27 @@ class CompilerTest(unittest.TestCase):
exec c in dct exec c in dct
self.assertEquals(dct.get('result'), 1) 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): def testGlobal(self):
code = compiler.compile('global x\nx=1', '<string>', 'exec') code = compiler.compile('global x\nx=1', '<string>', 'exec')
d1 = {'__builtins__': {}} d1 = {'__builtins__': {}}

View file

@ -199,6 +199,7 @@ class RoundtripLegalSyntaxTestCase(unittest.TestCase):
def test_with(self): def test_with(self):
self.check_suite("with open('x'): pass\n") 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: pass\n")
self.check_suite("with open('x') as f, open('y') as g: pass\n")
def test_try_stmt(self): def test_try_stmt(self):
self.check_suite("try: pass\nexcept: pass\n") self.check_suite("try: pass\nexcept: pass\n")

View file

@ -654,12 +654,88 @@ class ExitSwallowsExceptionTestCase(unittest.TestCase):
self.fail("ZeroDivisionError should have been raised") 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(): def test_main():
run_unittest(FailureTestCase, NonexceptionalTestCase, run_unittest(FailureTestCase, NonexceptionalTestCase,
NestedNonexceptionalTestCase, ExceptionalTestCase, NestedNonexceptionalTestCase, ExceptionalTestCase,
NonLocalFlowControlTestCase, NonLocalFlowControlTestCase,
AssignmentTargetTestCase, AssignmentTargetTestCase,
ExitSwallowsExceptionTestCase) ExitSwallowsExceptionTestCase,
NestedWith)
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -12,6 +12,8 @@ What's New in Python 2.7 alpha 1
Core and Builtins 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 - 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. statement and correctly lookup the __enter__ and __exit__ special methods.

View file

@ -2618,36 +2618,39 @@ validate_decorators(node *tree)
return ok; return ok;
} }
/* with_var /* with_item:
with_var: 'as' expr * test ['as' expr]
*/ */
static int static int
validate_with_var(node *tree) validate_with_item(node *tree)
{ {
int nch = NCH(tree); int nch = NCH(tree);
int ok = (validate_ntype(tree, with_var) int ok = (validate_ntype(tree, with_item)
&& (nch == 2) && (nch == 1 || nch == 3)
&& validate_name(CHILD(tree, 0), "as") && validate_test(CHILD(tree, 0)));
&& validate_expr(CHILD(tree, 1))); if (ok && nch == 3)
return ok; ok = (validate_name(CHILD(tree, 1), "as")
&& validate_expr(CHILD(tree, 2)));
return ok;
} }
/* with_stmt /* with_stmt:
* 0 1 2 -2 -1 * 0 1 ... -2 -1
with_stmt: 'with' test [ with_var ] ':' suite * 'with' with_item (',' with_item)* ':' suite
*/ */
static int static int
validate_with_stmt(node *tree) validate_with_stmt(node *tree)
{ {
int i;
int nch = NCH(tree); int nch = NCH(tree);
int ok = (validate_ntype(tree, with_stmt) int ok = (validate_ntype(tree, with_stmt)
&& ((nch == 4) || (nch == 5)) && (nch % 2 == 0)
&& validate_name(CHILD(tree, 0), "with") && 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_colon(RCHILD(tree, -2))
&& validate_suite(RCHILD(tree, -1))); && 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: /* funcdef:

View file

@ -3009,27 +3009,18 @@ ast_for_try_stmt(struct compiling *c, const node *n)
return TryFinally(body, finally, LINENO(n), n->n_col_offset, c->c_arena); return TryFinally(body, finally, LINENO(n), n->n_col_offset, c->c_arena);
} }
static expr_ty /* with_item: test ['as' expr] */
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 */
static stmt_ty 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; expr_ty context_expr, optional_vars = NULL;
int suite_index = 3; /* skip 'with', test, and ':' */
asdl_seq *suite_seq;
assert(TYPE(n) == with_stmt); REQ(n, with_item);
context_expr = ast_for_expr(c, CHILD(n, 1)); context_expr = ast_for_expr(c, CHILD(n, 0));
if (!context_expr) if (!context_expr)
return NULL; return NULL;
if (TYPE(CHILD(n, 2)) == with_var) { if (NCH(n) == 3) {
optional_vars = ast_for_with_var(c, CHILD(n, 2)); optional_vars = ast_for_expr(c, CHILD(n, 2));
if (!optional_vars) { if (!optional_vars) {
return NULL; return NULL;
@ -3037,17 +3028,47 @@ ast_for_with_stmt(struct compiling *c, const node *n)
if (!set_context(c, optional_vars, Store, n)) { if (!set_context(c, optional_vars, Store, n)) {
return NULL; return NULL;
} }
suite_index = 4;
} }
suite_seq = ast_for_suite(c, CHILD(n, suite_index)); return With(context_expr, optional_vars, content, LINENO(n),
if (!suite_seq) {
return NULL;
}
return With(context_expr, optional_vars, suite_seq, LINENO(n),
n->n_col_offset, c->c_arena); 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 ret;
}
static stmt_ty static stmt_ty
ast_for_classdef(struct compiling *c, const node *n, asdl_seq *decorator_seq) ast_for_classdef(struct compiling *c, const node *n, asdl_seq *decorator_seq)
{ {

View file

@ -901,42 +901,43 @@ static arc arcs_41_0[1] = {
{100, 1}, {100, 1},
}; };
static arc arcs_41_1[1] = { static arc arcs_41_1[1] = {
{28, 2}, {101, 2},
}; };
static arc arcs_41_2[2] = { static arc arcs_41_2[2] = {
{101, 3}, {29, 1},
{23, 4}, {23, 3},
}; };
static arc arcs_41_3[1] = { static arc arcs_41_3[1] = {
{23, 4}, {24, 4},
}; };
static arc arcs_41_4[1] = { static arc arcs_41_4[1] = {
{24, 5}, {0, 4},
}; };
static arc arcs_41_5[1] = { static state states_41[5] = {
{0, 5},
};
static state states_41[6] = {
{1, arcs_41_0}, {1, arcs_41_0},
{1, arcs_41_1}, {1, arcs_41_1},
{2, arcs_41_2}, {2, arcs_41_2},
{1, arcs_41_3}, {1, arcs_41_3},
{1, arcs_41_4}, {1, arcs_41_4},
{1, arcs_41_5},
}; };
static arc arcs_42_0[1] = { static arc arcs_42_0[1] = {
{80, 1}, {28, 1},
}; };
static arc arcs_42_1[1] = { static arc arcs_42_1[2] = {
{84, 2}, {80, 2},
{0, 1},
}; };
static arc arcs_42_2[1] = { static arc arcs_42_2[1] = {
{0, 2}, {84, 3},
}; };
static state states_42[3] = { static arc arcs_42_3[1] = {
{0, 3},
};
static state states_42[4] = {
{1, arcs_42_0}, {1, arcs_42_0},
{1, arcs_42_1}, {2, arcs_42_1},
{1, arcs_42_2}, {1, arcs_42_2},
{1, arcs_42_3},
}; };
static arc arcs_43_0[1] = { static arc arcs_43_0[1] = {
{102, 1}, {102, 1},
@ -1877,10 +1878,10 @@ static dfa dfas[85] = {
"\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000\000\000\000\000\000"}, "\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, {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"}, "\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"}, "\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, {298, "with_item", 0, 4, states_42,
"\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000\000\000\000\000\000\000\000"}, "\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, {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"}, "\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, {300, "suite", 0, 5, states_44,