FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Allow multiple context managers in one with statement, as proposed · python-emscripten/cpython@944f684 · GitHub

Repository navigation

Commit 944f684

Browse files
committed
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.
1 parent 0451661 commit 944f684

11 files changed

Lines changed: 215 additions & 70 deletions

File tree

‎Doc/reference/compound_stmts.rst‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,10 @@ allows common :keyword:`try`...\ :keyword:`except`...\ :keyword:`finally` usage
333333
patterns to be encapsulated for convenient reuse.
334334

335335
.. productionlist::
336-
with_stmt: "with" `expression` ["as" `target`] ":" `suite`
336+
with_stmt: "with" with_item ("," with_item)* ":" `suite`
337+
with_item: `expression` ["as" `target`]
337338

338-
The execution of the :keyword:`with` statement proceeds as follows:
339+
The execution of the :keyword:`with` statement with one "item" proceeds as follows:
339340

340341
#. The context expression is evaluated to obtain a context manager.
341342

@@ -369,12 +370,27 @@ The execution of the :keyword:`with` statement proceeds as follows:
369370
from :meth:`__exit__` is ignored, and execution proceeds at the normal location
370371
for the kind of exit that was taken.
371372

373+
With more than one item, the context managers are processed as if multiple
374+
:keyword:`with` statements were nested::
375+
376+
with A() as a, B() as b:
377+
suite
378+
379+
is equivalent to ::
380+
381+
with A() as a:
382+
with B() as b:
383+
suite
384+
372385
.. note::
373386

374387
In Python 2.5, the :keyword:`with` statement is only allowed when the
375388
``with_statement`` feature has been enabled. It is always enabled in
376389
Python 2.6.
377390

391+
.. versionchanged:: 2.7
392+
Support for multiple context expressions.
393+
378394
.. seealso::
379395

380396
:pep:`0343` - The "with" statement

‎Grammar/Grammar‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ try_stmt: ('try' ':' suite
8383
['else' ':' suite]
8484
['finally' ':' suite] |
8585
'finally' ':' suite))
86-
with_stmt: 'with' test [ with_var ] ':' suite
87-
with_var: 'as' expr
86+
with_stmt: 'with' with_item (',' with_item)* ':' suite
87+
with_item: test ['as' expr]
8888
# NB compile.c makes sure that the default except clause is last
8989
except_clause: 'except' [test [('as' | ',') test]]
9090
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT

‎Include/graminit.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
#define for_stmt 295
4343
#define try_stmt 296
4444
#define with_stmt 297
45-
#define with_var 298
45+
#define with_item 298
4646
#define except_clause 299
4747
#define suite 300
4848
#define testlist_safe 301

‎Lib/compiler/transformer.py‎

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -965,18 +965,22 @@ def com_try_except_finally(self, nodelist):
965965
return try_except
966966

967967
def com_with(self, nodelist):
968-
# with_stmt: 'with' expr [with_var] ':' suite
969-
expr = self.com_node(nodelist[1])
968+
# with_stmt: 'with' with_item (',' with_item)* ':' suite
970969
body = self.com_node(nodelist[-1])
971-
if nodelist[2][0] == token.COLON:
972-
var = None
970+
for i in range(len(nodelist) - 3, 0, -2):
971+
ret = self.com_with_item(nodelist[i], body, nodelist[0][2])
972+
if i == 1:
973+
return ret
974+
body = ret
975+
976+
def com_with_item(self, nodelist, body, lineno):
977+
# with_item: test ['as' expr]
978+
if len(nodelist) == 4:
979+
var = self.com_assign(nodelist[3], OP_ASSIGN)
973980
else:
974-
var = self.com_assign(nodelist[2][2], OP_ASSIGN)
975-
return With(expr, var, body, lineno=nodelist[0][2])
976-
977-
def com_with_var(self, nodelist):
978-
# with_var: 'as' expr
979-
return self.com_node(nodelist[1])
981+
var = None
982+
expr = self.com_node(nodelist[1])
983+
return With(expr, var, body, lineno=lineno)
980984

981985
def com_augassign_op(self, node):
982986
assert node[0] == symbol.augassign

‎Lib/test/test_compiler.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,27 @@ def testWithAss(self):
165165
exec c in dct
166166
self.assertEquals(dct.get('result'), 1)
167167

168+
def testWithMult(self):
169+
events = []
170+
class Ctx:
171+
def __init__(self, n):
172+
self.n = n
173+
def __enter__(self):
174+
events.append(self.n)
175+
def __exit__(self, *args):
176+
pass
177+
c = compiler.compile('from __future__ import with_statement\n'
178+
'def f():\n'
179+
' with Ctx(1) as tc, Ctx(2) as tc2:\n'
180+
' return 1\n'
181+
'result = f()',
182+
'<string>',
183+
'exec' )
184+
dct = {'Ctx': Ctx}
185+
exec c in dct
186+
self.assertEquals(dct.get('result'), 1)
187+
self.assertEquals(events, [1, 2])
188+
168189
def testGlobal(self):
169190
code = compiler.compile('global x\nx=1', '<string>', 'exec')
170191
d1 = {'__builtins__': {}}

‎Lib/test/test_parser.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ def test_assert(self):
199199
def test_with(self):
200200
self.check_suite("with open('x'): pass\n")
201201
self.check_suite("with open('x') as f: pass\n")
202+
self.check_suite("with open('x') as f, open('y') as g: pass\n")
202203

203204
def test_try_stmt(self):
204205
self.check_suite("try: pass\nexcept: pass\n")

‎Lib/test/test_with.py‎

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,12 +654,88 @@ def __exit__(self, t, v, tb): return False
654654
self.fail("ZeroDivisionError should have been raised")
655655

656656

657+
class NestedWith(unittest.TestCase):
658+
659+
class Dummy(object):
660+
def __init__(self, value=None, gobble=False):
661+
if value is None:
662+
value = self
663+
self.value = value
664+
self.gobble = gobble
665+
self.enter_called = False
666+
self.exit_called = False
667+
668+
def __enter__(self):
669+
self.enter_called = True
670+
return self.value
671+
672+
def __exit__(self, *exc_info):
673+
self.exit_called = True
674+
self.exc_info = exc_info
675+
if self.gobble:
676+
return True
677+
678+
class CtorRaises(object):
679+
def __init__(self): raise RuntimeError()
680+
681+
class EnterRaises(object):
682+
def __enter__(self): raise RuntimeError()
683+
def __exit__(self, *exc_info): pass
684+
685+
class ExitRaises(object):
686+
def __enter__(self): pass
687+
def __exit__(self, *exc_info): raise RuntimeError()
688+
689+
def testNoExceptions(self):
690+
with self.Dummy() as a, self.Dummy() as b:
691+
self.assertTrue(a.enter_called)
692+
self.assertTrue(b.enter_called)
693+
self.assertTrue(a.exit_called)
694+
self.assertTrue(b.exit_called)
695+
696+
def testExceptionInExprList(self):
697+
try:
698+
with self.Dummy() as a, self.CtorRaises():
699+
pass
700+
except:
701+
pass
702+
self.assertTrue(a.enter_called)
703+
self.assertTrue(a.exit_called)
704+
705+
def testExceptionInEnter(self):
706+
try:
707+
with self.Dummy() as a, self.EnterRaises():
708+
self.fail('body of bad with executed')
709+
except RuntimeError:
710+
pass
711+
else:
712+
self.fail('RuntimeError not reraised')
713+
self.assertTrue(a.enter_called)
714+
self.assertTrue(a.exit_called)
715+
716+
def testExceptionInExit(self):
717+
body_executed = False
718+
with self.Dummy(gobble=True) as a, self.ExitRaises():
719+
body_executed = True
720+
self.assertTrue(a.enter_called)
721+
self.assertTrue(a.exit_called)
722+
self.assertNotEqual(a.exc_info[0], None)
723+
724+
def testEnterReturnsTuple(self):
725+
with self.Dummy(value=(1,2)) as (a1, a2), \
726+
self.Dummy(value=(10, 20)) as (b1, b2):
727+
self.assertEquals(1, a1)
728+
self.assertEquals(2, a2)
729+
self.assertEquals(10, b1)
730+
self.assertEquals(20, b2)
731+
657732
def test_main():
658733
run_unittest(FailureTestCase, NonexceptionalTestCase,
659734
NestedNonexceptionalTestCase, ExceptionalTestCase,
660735
NonLocalFlowControlTestCase,
661736
AssignmentTargetTestCase,
662-
ExitSwallowsExceptionTestCase)
737+
ExitSwallowsExceptionTestCase,
738+
NestedWith)
663739

664740

665741
if __name__ == '__main__':

‎Misc/NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ What's New in Python 2.7 alpha 1
1212
Core and Builtins
1313
-----------------
1414

15+
- Added support for multiple context managers in the same with statement.
16+
1517
- Issue #6101: A new opcode, SETUP_WITH, has been added to speed up the with
1618
statement and correctly lookup the __enter__ and __exit__ special methods.
1719

‎Modules/parsermodule.c‎

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2618,36 +2618,39 @@ validate_decorators(node *tree)
26182618
return ok;
26192619
}
26202620

2621-
/* with_var
2622-
with_var: 'as' expr
2621+
/* with_item:
2622+
* test ['as' expr]
26232623
*/
26242624
static int
2625-
validate_with_var(node *tree)
2625+
validate_with_item(node *tree)
26262626
{
26272627
int nch = NCH(tree);
2628-
int ok = (validate_ntype(tree, with_var)
2629-
&& (nch == 2)
2630-
&& validate_name(CHILD(tree, 0), "as")
2631-
&& validate_expr(CHILD(tree, 1)));
2632-
return ok;
2628+
int ok = (validate_ntype(tree, with_item)
2629+
&& (nch == 1 || nch == 3)
2630+
&& validate_test(CHILD(tree, 0)));
2631+
if (ok && nch == 3)
2632+
ok = (validate_name(CHILD(tree, 1), "as")
2633+
&& validate_expr(CHILD(tree, 2)));
2634+
return ok;
26332635
}
26342636

2635-
/* with_stmt
2636-
* 0 1 2 -2 -1
2637-
with_stmt: 'with' test [ with_var ] ':' suite
2637+
/* with_stmt:
2638+
* 0 1 ... -2 -1
2639+
* 'with' with_item (',' with_item)* ':' suite
26382640
*/
26392641
static int
26402642
validate_with_stmt(node *tree)
26412643
{
2644+
int i;
26422645
int nch = NCH(tree);
26432646
int ok = (validate_ntype(tree, with_stmt)
2644-
&& ((nch == 4) || (nch == 5))
2647+
&& (nch % 2 == 0)
26452648
&& validate_name(CHILD(tree, 0), "with")
2646-
&& validate_test(CHILD(tree, 1))
2647-
&& (nch == 4 || validate_with_var(CHILD(tree, 2)))
26482649
&& validate_colon(RCHILD(tree, -2))
26492650
&& validate_suite(RCHILD(tree, -1)));
2650-
return ok;
2651+
for (i = 1; ok && i < nch - 2; i += 2)
2652+
ok = validate_with_item(CHILD(tree, i));
2653+
return ok;
26512654
}
26522655

26532656
/* funcdef:

‎Python/ast.c‎

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3009,43 +3009,64 @@ ast_for_try_stmt(struct compiling *c, const node *n)
30093009
return TryFinally(body, finally, LINENO(n), n->n_col_offset, c->c_arena);
30103010
}
30113011

3012-
static expr_ty
3013-
ast_for_with_var(struct compiling *c, const node *n)
3014-
{
3015-
REQ(n, with_var);
3016-
return ast_for_expr(c, CHILD(n, 1));
3017-
}
3018-
3019-
/* with_stmt: 'with' test [ with_var ] ':' suite */
3012+
/* with_item: test ['as' expr] */
30203013
static stmt_ty
3021-
ast_for_with_stmt(struct compiling *c, const node *n)
3014+
ast_for_with_item(struct compiling *c, const node *n, asdl_seq *content)
30223015
{
30233016
expr_ty context_expr, optional_vars = NULL;
3024-
int suite_index = 3; /* skip 'with', test, and ':' */
3025-
asdl_seq *suite_seq;
30263017

3027-
assert(TYPE(n) == with_stmt);
3028-
context_expr = ast_for_expr(c, CHILD(n, 1));
3018+
REQ(n, with_item);
3019+
context_expr = ast_for_expr(c, CHILD(n, 0));
30293020
if (!context_expr)
30303021
return NULL;
3031-
if (TYPE(CHILD(n, 2)) == with_var) {
3032-
optional_vars = ast_for_with_var(c, CHILD(n, 2));
3022+
if (NCH(n) == 3) {
3023+
optional_vars = ast_for_expr(c, CHILD(n, 2));
30333024

30343025
if (!optional_vars) {
30353026
return NULL;
30363027
}
30373028
if (!set_context(c, optional_vars, Store, n)) {
30383029
return NULL;
30393030
}
3040-
suite_index = 4;
30413031
}
30423032

3043-
suite_seq = ast_for_suite(c, CHILD(n, suite_index));
3044-
if (!suite_seq) {
3033+
return With(context_expr, optional_vars, content, LINENO(n),
3034+
n->n_col_offset, c->c_arena);
3035+
}
3036+
3037+
/* with_stmt: 'with' with_item (',' with_item)* ':' suite */
3038+
static stmt_ty
3039+
ast_for_with_stmt(struct compiling *c, const node *n)
3040+
{
3041+
int i;
3042+
stmt_ty ret;
3043+
asdl_seq *inner;
3044+
3045+
REQ(n, with_stmt);
3046+
3047+
/* process the with items inside-out */
3048+
i = NCH(n) - 1;
3049+
/* the suite of the innermost with item is the suite of the with stmt */
3050+
inner = ast_for_suite(c, CHILD(n, i));
3051+
if (!inner)
30453052
return NULL;
3053+
3054+
for (;;) {
3055+
i -= 2;
3056+
ret = ast_for_with_item(c, CHILD(n, i), inner);
3057+
if (!ret)
3058+
return NULL;
3059+
/* was this the last item? */
3060+
if (i == 1)
3061+
break;
3062+
/* if not, wrap the result so far in a new sequence */
3063+
inner = asdl_seq_new(1, c->c_arena);
3064+
if (!inner)
3065+
return NULL;
3066+
asdl_seq_SET(inner, 0, ret);
30463067
}
3047-
return With(context_expr, optional_vars, suite_seq, LINENO(n),
3048-
n->n_col_offset, c->c_arena);
3068+
3069+
return ret;
30493070
}
30503071

30513072
static stmt_ty

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL