PEP 343 -- the with-statement.

This was started by Mike Bland and completed by Guido
(with help from Neal).

This still needs a __future__ statement added;
Thomas is working on Michael's patch for that aspect.

There's a small amount of code cleanup and refactoring
in ast.c, compile.c and ceval.c (I fixed the lltrace
behavior when EXT_POP is used -- however I had to make
lltrace a static global).
This commit is contained in:
Guido van Rossum 2006-02-27 22:32:47 +00:00
parent 5fec904f84
commit c2e20744b2
23 changed files with 1853 additions and 816 deletions

View file

@ -272,6 +272,11 @@
\lineiii{}{\member{else_}}{} \lineiii{}{\member{else_}}{}
\hline \hline
\lineiii{With}{\member{expr}}{}
\lineiii{}{\member{vars&}}{}
\lineiii{}{\member{body}}{}
\hline
\lineiii{Yield}{\member{value}}{} \lineiii{Yield}{\member{value}}{}
\hline \hline

View file

@ -308,6 +308,12 @@ section~\ref{exceptions}, and information on using the \keyword{raise}
statement to generate exceptions may be found in section~\ref{raise}. statement to generate exceptions may be found in section~\ref{raise}.
\section{The \keyword{with} statement\label{with}}
\stindex{with}
The \keyword{with} statement specifies
\section{Function definitions\label{function}} \section{Function definitions\label{function}}
\indexii{function}{definition} \indexii{function}{definition}
\stindex{def} \stindex{def}

View file

@ -70,7 +70,7 @@ global_stmt: 'global' NAME (',' NAME)*
exec_stmt: 'exec' expr ['in' test [',' test]] exec_stmt: 'exec' expr ['in' test [',' test]]
assert_stmt: 'assert' test [',' test] assert_stmt: 'assert' test [',' test]
compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
while_stmt: 'while' test ':' suite ['else' ':' suite] while_stmt: 'while' test ':' suite ['else' ':' suite]
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite] for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
@ -79,6 +79,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_var: NAME 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 [',' test]] except_clause: 'except' [test [',' test]]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT

View file

@ -61,11 +61,11 @@ struct _mod {
struct _stmt { struct _stmt {
enum { FunctionDef_kind=1, ClassDef_kind=2, Return_kind=3, enum { FunctionDef_kind=1, ClassDef_kind=2, Return_kind=3,
Delete_kind=4, Assign_kind=5, AugAssign_kind=6, Print_kind=7, Delete_kind=4, Assign_kind=5, AugAssign_kind=6, Print_kind=7,
For_kind=8, While_kind=9, If_kind=10, Raise_kind=11, For_kind=8, While_kind=9, If_kind=10, With_kind=11,
TryExcept_kind=12, TryFinally_kind=13, Assert_kind=14, Raise_kind=12, TryExcept_kind=13, TryFinally_kind=14,
Import_kind=15, ImportFrom_kind=16, Exec_kind=17, Assert_kind=15, Import_kind=16, ImportFrom_kind=17,
Global_kind=18, Expr_kind=19, Pass_kind=20, Break_kind=21, Exec_kind=18, Global_kind=19, Expr_kind=20, Pass_kind=21,
Continue_kind=22 } kind; Break_kind=22, Continue_kind=23 } kind;
union { union {
struct { struct {
identifier name; identifier name;
@ -124,6 +124,12 @@ struct _stmt {
asdl_seq *orelse; asdl_seq *orelse;
} If; } If;
struct {
expr_ty context_expr;
expr_ty optional_vars;
asdl_seq *body;
} With;
struct { struct {
expr_ty type; expr_ty type;
expr_ty inst; expr_ty inst;
@ -355,6 +361,8 @@ stmt_ty While(expr_ty test, asdl_seq * body, asdl_seq * orelse, int lineno,
PyArena *arena); PyArena *arena);
stmt_ty If(expr_ty test, asdl_seq * body, asdl_seq * orelse, int lineno, stmt_ty If(expr_ty test, asdl_seq * body, asdl_seq * orelse, int lineno,
PyArena *arena); PyArena *arena);
stmt_ty With(expr_ty context_expr, expr_ty optional_vars, asdl_seq * body, int
lineno, PyArena *arena);
stmt_ty Raise(expr_ty type, expr_ty inst, expr_ty tback, int lineno, PyArena stmt_ty Raise(expr_ty type, expr_ty inst, expr_ty tback, int lineno, PyArena
*arena); *arena);
stmt_ty TryExcept(asdl_seq * body, asdl_seq * handlers, asdl_seq * orelse, int stmt_ty TryExcept(asdl_seq * body, asdl_seq * handlers, asdl_seq * orelse, int

View file

@ -38,45 +38,47 @@
#define while_stmt 293 #define while_stmt 293
#define for_stmt 294 #define for_stmt 294
#define try_stmt 295 #define try_stmt 295
#define except_clause 296 #define with_stmt 296
#define suite 297 #define with_var 297
#define testlist_safe 298 #define except_clause 298
#define old_test 299 #define suite 299
#define old_lambdef 300 #define testlist_safe 300
#define test 301 #define old_test 301
#define or_test 302 #define old_lambdef 302
#define and_test 303 #define test 303
#define not_test 304 #define or_test 304
#define comparison 305 #define and_test 305
#define comp_op 306 #define not_test 306
#define expr 307 #define comparison 307
#define xor_expr 308 #define comp_op 308
#define and_expr 309 #define expr 309
#define shift_expr 310 #define xor_expr 310
#define arith_expr 311 #define and_expr 311
#define term 312 #define shift_expr 312
#define factor 313 #define arith_expr 313
#define power 314 #define term 314
#define atom 315 #define factor 315
#define listmaker 316 #define power 316
#define testlist_gexp 317 #define atom 317
#define lambdef 318 #define listmaker 318
#define trailer 319 #define testlist_gexp 319
#define subscriptlist 320 #define lambdef 320
#define subscript 321 #define trailer 321
#define sliceop 322 #define subscriptlist 322
#define exprlist 323 #define subscript 323
#define testlist 324 #define sliceop 324
#define dictmaker 325 #define exprlist 325
#define classdef 326 #define testlist 326
#define arglist 327 #define dictmaker 327
#define argument 328 #define classdef 328
#define list_iter 329 #define arglist 329
#define list_for 330 #define argument 330
#define list_if 331 #define list_iter 331
#define gen_iter 332 #define list_for 332
#define gen_for 333 #define list_if 333
#define gen_if 334 #define gen_iter 334
#define testlist1 335 #define gen_for 335
#define encoding_decl 336 #define gen_if 336
#define yield_expr 337 #define testlist1 337
#define encoding_decl 338
#define yield_expr 339

View file

@ -72,13 +72,12 @@ extern "C" {
#define INPLACE_XOR 78 #define INPLACE_XOR 78
#define INPLACE_OR 79 #define INPLACE_OR 79
#define BREAK_LOOP 80 #define BREAK_LOOP 80
#define WITH_CLEANUP 81
#define LOAD_LOCALS 82 #define LOAD_LOCALS 82
#define RETURN_VALUE 83 #define RETURN_VALUE 83
#define IMPORT_STAR 84 #define IMPORT_STAR 84
#define EXEC_STMT 85 #define EXEC_STMT 85
#define YIELD_VALUE 86 #define YIELD_VALUE 86
#define POP_BLOCK 87 #define POP_BLOCK 87
#define END_FINALLY 88 #define END_FINALLY 88
#define BUILD_CLASS 89 #define BUILD_CLASS 89

View file

@ -553,7 +553,7 @@ class Function(Node):
self.varargs = 1 self.varargs = 1
if flags & CO_VARKEYWORDS: if flags & CO_VARKEYWORDS:
self.kwargs = 1 self.kwargs = 1
def getChildren(self): def getChildren(self):
@ -584,7 +584,7 @@ class GenExpr(Node):
self.lineno = lineno self.lineno = lineno
self.argnames = ['[outmost-iterable]'] self.argnames = ['[outmost-iterable]']
self.varargs = self.kwargs = None self.varargs = self.kwargs = None
def getChildren(self): def getChildren(self):
@ -763,7 +763,7 @@ class Lambda(Node):
self.varargs = 1 self.varargs = 1
if flags & CO_VARKEYWORDS: if flags & CO_VARKEYWORDS:
self.kwargs = 1 self.kwargs = 1
def getChildren(self): def getChildren(self):
@ -1297,6 +1297,31 @@ class While(Node):
def __repr__(self): def __repr__(self):
return "While(%s, %s, %s)" % (repr(self.test), repr(self.body), repr(self.else_)) return "While(%s, %s, %s)" % (repr(self.test), repr(self.body), repr(self.else_))
class With(Node):
def __init__(self, expr, vars, body, lineno=None):
self.expr = expr
self.vars = vars
self.body = body
self.lineno = lineno
def getChildren(self):
children = []
children.append(self.expr)
children.append(self.vars)
children.append(self.body)
return tuple(children)
def getChildNodes(self):
nodelist = []
nodelist.append(self.expr)
if self.vars is not None:
nodelist.append(self.vars)
nodelist.append(self.body)
return tuple(nodelist)
def __repr__(self):
return "With(%s, %s, %s)" % (repr(self.expr), repr(self.vars), repr(self.body))
class Yield(Node): class Yield(Node):
def __init__(self, value, lineno=None): def __init__(self, value, lineno=None):
self.value = value self.value = value

View file

@ -41,6 +41,7 @@ def jabs_op(name, op):
hasjabs.append(op) hasjabs.append(op)
# Instruction opcodes for compiled code # Instruction opcodes for compiled code
# Blank lines correspond to available opcodes
def_op('STOP_CODE', 0) def_op('STOP_CODE', 0)
def_op('POP_TOP', 1) def_op('POP_TOP', 1)
@ -59,7 +60,6 @@ def_op('UNARY_INVERT', 15)
def_op('LIST_APPEND', 18) def_op('LIST_APPEND', 18)
def_op('BINARY_POWER', 19) def_op('BINARY_POWER', 19)
def_op('BINARY_MULTIPLY', 20) def_op('BINARY_MULTIPLY', 20)
def_op('BINARY_DIVIDE', 21) def_op('BINARY_DIVIDE', 21)
def_op('BINARY_MODULO', 22) def_op('BINARY_MODULO', 22)
@ -70,7 +70,6 @@ def_op('BINARY_FLOOR_DIVIDE', 26)
def_op('BINARY_TRUE_DIVIDE', 27) def_op('BINARY_TRUE_DIVIDE', 27)
def_op('INPLACE_FLOOR_DIVIDE', 28) def_op('INPLACE_FLOOR_DIVIDE', 28)
def_op('INPLACE_TRUE_DIVIDE', 29) def_op('INPLACE_TRUE_DIVIDE', 29)
def_op('SLICE+0', 30) def_op('SLICE+0', 30)
def_op('SLICE+1', 31) def_op('SLICE+1', 31)
def_op('SLICE+2', 32) def_op('SLICE+2', 32)
@ -93,7 +92,6 @@ def_op('INPLACE_DIVIDE', 58)
def_op('INPLACE_MODULO', 59) def_op('INPLACE_MODULO', 59)
def_op('STORE_SUBSCR', 60) def_op('STORE_SUBSCR', 60)
def_op('DELETE_SUBSCR', 61) def_op('DELETE_SUBSCR', 61)
def_op('BINARY_LSHIFT', 62) def_op('BINARY_LSHIFT', 62)
def_op('BINARY_RSHIFT', 63) def_op('BINARY_RSHIFT', 63)
def_op('BINARY_AND', 64) def_op('BINARY_AND', 64)
@ -113,13 +111,12 @@ def_op('INPLACE_AND', 77)
def_op('INPLACE_XOR', 78) def_op('INPLACE_XOR', 78)
def_op('INPLACE_OR', 79) def_op('INPLACE_OR', 79)
def_op('BREAK_LOOP', 80) def_op('BREAK_LOOP', 80)
def_op('WITH_CLEANUP', 81)
def_op('LOAD_LOCALS', 82) def_op('LOAD_LOCALS', 82)
def_op('RETURN_VALUE', 83) def_op('RETURN_VALUE', 83)
def_op('IMPORT_STAR', 84) def_op('IMPORT_STAR', 84)
def_op('EXEC_STMT', 85) def_op('EXEC_STMT', 85)
def_op('YIELD_VALUE', 86) def_op('YIELD_VALUE', 86)
def_op('POP_BLOCK', 87) def_op('POP_BLOCK', 87)
def_op('END_FINALLY', 88) def_op('END_FINALLY', 88)
def_op('BUILD_CLASS', 89) def_op('BUILD_CLASS', 89)
@ -171,7 +168,6 @@ def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3)
def_op('CALL_FUNCTION', 131) # #args + (#kwargs << 8) def_op('CALL_FUNCTION', 131) # #args + (#kwargs << 8)
def_op('MAKE_FUNCTION', 132) # Number of args with default values def_op('MAKE_FUNCTION', 132) # Number of args with default values
def_op('BUILD_SLICE', 133) # Number of items def_op('BUILD_SLICE', 133) # Number of items
def_op('MAKE_CLOSURE', 134) def_op('MAKE_CLOSURE', 134)
def_op('LOAD_CLOSURE', 135) def_op('LOAD_CLOSURE', 135)
hasfree.append(135) hasfree.append(135)
@ -183,7 +179,6 @@ hasfree.append(137)
def_op('CALL_FUNCTION_VAR', 140) # #args + (#kwargs << 8) def_op('CALL_FUNCTION_VAR', 140) # #args + (#kwargs << 8)
def_op('CALL_FUNCTION_KW', 141) # #args + (#kwargs << 8) def_op('CALL_FUNCTION_KW', 141) # #args + (#kwargs << 8)
def_op('CALL_FUNCTION_VAR_KW', 142) # #args + (#kwargs << 8) def_op('CALL_FUNCTION_VAR_KW', 142) # #args + (#kwargs << 8)
def_op('EXTENDED_ARG', 143) def_op('EXTENDED_ARG', 143)
EXTENDED_ARG = 143 EXTENDED_ARG = 143

View file

@ -50,48 +50,50 @@ if_stmt = 292
while_stmt = 293 while_stmt = 293
for_stmt = 294 for_stmt = 294
try_stmt = 295 try_stmt = 295
except_clause = 296 with_stmt = 296
suite = 297 with_var = 297
testlist_safe = 298 except_clause = 298
old_test = 299 suite = 299
old_lambdef = 300 testlist_safe = 300
test = 301 old_test = 301
or_test = 302 old_lambdef = 302
and_test = 303 test = 303
not_test = 304 or_test = 304
comparison = 305 and_test = 305
comp_op = 306 not_test = 306
expr = 307 comparison = 307
xor_expr = 308 comp_op = 308
and_expr = 309 expr = 309
shift_expr = 310 xor_expr = 310
arith_expr = 311 and_expr = 311
term = 312 shift_expr = 312
factor = 313 arith_expr = 313
power = 314 term = 314
atom = 315 factor = 315
listmaker = 316 power = 316
testlist_gexp = 317 atom = 317
lambdef = 318 listmaker = 318
trailer = 319 testlist_gexp = 319
subscriptlist = 320 lambdef = 320
subscript = 321 trailer = 321
sliceop = 322 subscriptlist = 322
exprlist = 323 subscript = 323
testlist = 324 sliceop = 324
dictmaker = 325 exprlist = 325
classdef = 326 testlist = 326
arglist = 327 dictmaker = 327
argument = 328 classdef = 328
list_iter = 329 arglist = 329
list_for = 330 argument = 330
list_if = 331 list_iter = 331
gen_iter = 332 list_for = 332
gen_for = 333 list_if = 333
gen_if = 334 gen_iter = 334
testlist1 = 335 gen_for = 335
encoding_decl = 336 gen_if = 336
yield_expr = 337 testlist1 = 337
encoding_decl = 338
yield_expr = 339
#--end constants-- #--end constants--
sym_name = {} sym_name = {}

View file

@ -0,0 +1,34 @@
class GeneratorContextManager(object):
def __init__(self, gen):
self.gen = gen
def __context__(self):
return self
def __enter__(self):
try:
return self.gen.next()
except StopIteration:
raise RuntimeError("generator didn't yield")
def __exit__(self, type, value, traceback):
if type is None:
try:
self.gen.next()
except StopIteration:
return
else:
raise RuntimeError("generator didn't stop")
else:
try:
self.gen.throw(type, value, traceback)
except (type, StopIteration):
return
else:
raise RuntimeError("generator caught exception")
def contextmanager(func):
def helper(*args, **kwds):
return GeneratorContextManager(func(*args, **kwds))
return helper

41
Lib/test/nested.py Normal file
View file

@ -0,0 +1,41 @@
import sys
from collections import deque
class nested(object):
def __init__(self, *contexts):
self.contexts = contexts
self.entered = None
def __context__(self):
return self
def __enter__(self):
if self.entered is not None:
raise RuntimeError("Context is not reentrant")
self.entered = deque()
vars = []
try:
for context in self.contexts:
mgr = context.__context__()
vars.append(mgr.__enter__())
self.entered.appendleft(mgr)
except:
self.__exit__(*sys.exc_info())
raise
return vars
def __exit__(self, *exc_info):
# Behave like nested with statements
# first in, last out
# New exceptions override old ones
ex = exc_info
for mgr in self.entered:
try:
mgr.__exit__(*ex)
except:
ex = sys.exc_info()
self.entered = None
if ex is not exc_info:
raise ex[0], ex[1], ex[2]

560
Lib/test/test_with.py Normal file
View file

@ -0,0 +1,560 @@
#!/usr/bin/env python
"""Unit tests for the with statement specified in PEP 343."""
__author__ = "Mike Bland"
__email__ = "mbland at acm dot org"
import unittest
from test.contextmanager import GeneratorContextManager
from test.nested import nested
from test.test_support import run_unittest
class MockContextManager(GeneratorContextManager):
def __init__(self, gen):
GeneratorContextManager.__init__(self, gen)
self.context_called = False
self.enter_called = False
self.exit_called = False
self.exit_args = None
def __context__(self):
self.context_called = True
return GeneratorContextManager.__context__(self)
def __enter__(self):
self.enter_called = True
return GeneratorContextManager.__enter__(self)
def __exit__(self, type, value, traceback):
self.exit_called = True
self.exit_args = (type, value, traceback)
return GeneratorContextManager.__exit__(self, type, value, traceback)
def mock_contextmanager(func):
def helper(*args, **kwds):
return MockContextManager(func(*args, **kwds))
return helper
class MockResource(object):
def __init__(self):
self.yielded = False
self.stopped = False
@mock_contextmanager
def mock_contextmanager_generator():
mock = MockResource()
try:
mock.yielded = True
yield mock
finally:
mock.stopped = True
class MockNested(nested):
def __init__(self, *contexts):
nested.__init__(self, *contexts)
self.context_called = False
self.enter_called = False
self.exit_called = False
self.exit_args = None
def __context__(self):
self.context_called = True
return nested.__context__(self)
def __enter__(self):
self.enter_called = True
return nested.__enter__(self)
def __exit__(self, *exc_info):
self.exit_called = True
self.exit_args = exc_info
return nested.__exit__(self, *exc_info)
class FailureTestCase(unittest.TestCase):
def testNameError(self):
def fooNotDeclared():
with foo: pass
self.assertRaises(NameError, fooNotDeclared)
def testContextAttributeError(self):
class LacksContext(object):
def __enter__(self):
pass
def __exit__(self, type, value, traceback):
pass
def fooLacksContext():
foo = LacksContext()
with foo: pass
self.assertRaises(AttributeError, fooLacksContext)
def testEnterAttributeError(self):
class LacksEnter(object):
def __context__(self):
pass
def __exit__(self, type, value, traceback):
pass
def fooLacksEnter():
foo = LacksEnter()
with foo: pass
self.assertRaises(AttributeError, fooLacksEnter)
def testExitAttributeError(self):
class LacksExit(object):
def __context__(self):
pass
def __enter__(self):
pass
def fooLacksExit():
foo = LacksExit()
with foo: pass
self.assertRaises(AttributeError, fooLacksExit)
def assertRaisesSyntaxError(self, codestr):
def shouldRaiseSyntaxError(s):
compile(s, '', 'single')
self.assertRaises(SyntaxError, shouldRaiseSyntaxError, codestr)
def testAssignmentToNoneError(self):
self.assertRaisesSyntaxError('with mock as None:\n pass')
self.assertRaisesSyntaxError(
'with mock as (None):\n'
' pass')
def testAssignmentToEmptyTupleError(self):
self.assertRaisesSyntaxError(
'with mock as ():\n'
' pass')
def testAssignmentToTupleOnlyContainingNoneError(self):
self.assertRaisesSyntaxError('with mock as None,:\n pass')
self.assertRaisesSyntaxError(
'with mock as (None,):\n'
' pass')
def testAssignmentToTupleContainingNoneError(self):
self.assertRaisesSyntaxError(
'with mock as (foo, None, bar):\n'
' pass')
def testContextThrows(self):
class ContextThrows(object):
def __context__(self):
raise RuntimeError("Context threw")
def shouldThrow():
ct = ContextThrows()
self.foo = None
with ct as self.foo:
pass
self.assertRaises(RuntimeError, shouldThrow)
self.assertEqual(self.foo, None)
def testEnterThrows(self):
class EnterThrows(object):
def __context__(self):
return self
def __enter__(self):
raise RuntimeError("Context threw")
def __exit__(self, *args):
pass
def shouldThrow():
ct = EnterThrows()
self.foo = None
with ct as self.foo:
pass
self.assertRaises(RuntimeError, shouldThrow)
self.assertEqual(self.foo, None)
def testExitThrows(self):
class ExitThrows(object):
def __context__(self):
return self
def __enter__(self):
return
def __exit__(self, *args):
raise RuntimeError(42)
def shouldThrow():
with ExitThrows():
pass
self.assertRaises(RuntimeError, shouldThrow)
class ContextmanagerAssertionMixin(object):
TEST_EXCEPTION = RuntimeError("test exception")
def assertInWithManagerInvariants(self, mock_manager):
self.assertTrue(mock_manager.context_called)
self.assertTrue(mock_manager.enter_called)
self.assertFalse(mock_manager.exit_called)
self.assertEqual(mock_manager.exit_args, None)
def assertAfterWithManagerInvariants(self, mock_manager, exit_args):
self.assertTrue(mock_manager.context_called)
self.assertTrue(mock_manager.enter_called)
self.assertTrue(mock_manager.exit_called)
self.assertEqual(mock_manager.exit_args, exit_args)
def assertAfterWithManagerInvariantsNoError(self, mock_manager):
self.assertAfterWithManagerInvariants(mock_manager,
(None, None, None))
def assertInWithGeneratorInvariants(self, mock_generator):
self.assertTrue(mock_generator.yielded)
self.assertFalse(mock_generator.stopped)
def assertAfterWithGeneratorInvariantsNoError(self, mock_generator):
self.assertTrue(mock_generator.yielded)
self.assertTrue(mock_generator.stopped)
def raiseTestException(self):
raise self.TEST_EXCEPTION
def assertAfterWithManagerInvariantsWithError(self, mock_manager):
self.assertTrue(mock_manager.context_called)
self.assertTrue(mock_manager.enter_called)
self.assertTrue(mock_manager.exit_called)
self.assertEqual(mock_manager.exit_args[0], RuntimeError)
self.assertEqual(mock_manager.exit_args[1], self.TEST_EXCEPTION)
def assertAfterWithGeneratorInvariantsWithError(self, mock_generator):
self.assertTrue(mock_generator.yielded)
self.assertTrue(mock_generator.stopped)
class NonexceptionalTestCase(unittest.TestCase, ContextmanagerAssertionMixin):
def testInlineGeneratorSyntax(self):
with mock_contextmanager_generator():
pass
def testUnboundGenerator(self):
mock = mock_contextmanager_generator()
with mock:
pass
self.assertAfterWithManagerInvariantsNoError(mock)
def testInlineGeneratorBoundSyntax(self):
with mock_contextmanager_generator() as foo:
self.assertInWithGeneratorInvariants(foo)
# FIXME: In the future, we'll try to keep the bound names from leaking
self.assertAfterWithGeneratorInvariantsNoError(foo)
def testInlineGeneratorBoundToExistingVariable(self):
foo = None
with mock_contextmanager_generator() as foo:
self.assertInWithGeneratorInvariants(foo)
self.assertAfterWithGeneratorInvariantsNoError(foo)
def testInlineGeneratorBoundToDottedVariable(self):
with mock_contextmanager_generator() as self.foo:
self.assertInWithGeneratorInvariants(self.foo)
self.assertAfterWithGeneratorInvariantsNoError(self.foo)
def testBoundGenerator(self):
mock = mock_contextmanager_generator()
with mock as foo:
self.assertInWithGeneratorInvariants(foo)
self.assertInWithManagerInvariants(mock)
self.assertAfterWithGeneratorInvariantsNoError(foo)
self.assertAfterWithManagerInvariantsNoError(mock)
def testNestedSingleStatements(self):
mock_a = mock_contextmanager_generator()
with mock_a as foo:
mock_b = mock_contextmanager_generator()
with mock_b as bar:
self.assertInWithManagerInvariants(mock_a)
self.assertInWithManagerInvariants(mock_b)
self.assertInWithGeneratorInvariants(foo)
self.assertInWithGeneratorInvariants(bar)
self.assertAfterWithManagerInvariantsNoError(mock_b)
self.assertAfterWithGeneratorInvariantsNoError(bar)
self.assertInWithManagerInvariants(mock_a)
self.assertInWithGeneratorInvariants(foo)
self.assertAfterWithManagerInvariantsNoError(mock_a)
self.assertAfterWithGeneratorInvariantsNoError(foo)
class NestedNonexceptionalTestCase(unittest.TestCase,
ContextmanagerAssertionMixin):
def testSingleArgInlineGeneratorSyntax(self):
with nested(mock_contextmanager_generator()):
pass
def testSingleArgUnbound(self):
mock_contextmanager = mock_contextmanager_generator()
mock_nested = MockNested(mock_contextmanager)
with mock_nested:
self.assertInWithManagerInvariants(mock_contextmanager)
self.assertInWithManagerInvariants(mock_nested)
self.assertAfterWithManagerInvariantsNoError(mock_contextmanager)
self.assertAfterWithManagerInvariantsNoError(mock_nested)
def testSingleArgBoundToNonTuple(self):
m = mock_contextmanager_generator()
# This will bind all the arguments to nested() into a single list
# assigned to foo.
with nested(m) as foo:
self.assertInWithManagerInvariants(m)
self.assertAfterWithManagerInvariantsNoError(m)
def testSingleArgBoundToSingleElementParenthesizedList(self):
m = mock_contextmanager_generator()
# This will bind all the arguments to nested() into a single list
# assigned to foo.
# FIXME: what should this do: with nested(m) as (foo,):
with nested(m) as (foo):
self.assertInWithManagerInvariants(m)
self.assertAfterWithManagerInvariantsNoError(m)
def testSingleArgBoundToMultipleElementTupleError(self):
def shouldThrowValueError():
with nested(mock_contextmanager_generator()) as (foo, bar):
pass
self.assertRaises(ValueError, shouldThrowValueError)
def testSingleArgUnbound(self):
mock_contextmanager = mock_contextmanager_generator()
mock_nested = MockNested(mock_contextmanager)
with mock_nested:
self.assertInWithManagerInvariants(mock_contextmanager)
self.assertInWithManagerInvariants(mock_nested)
self.assertAfterWithManagerInvariantsNoError(mock_contextmanager)
self.assertAfterWithManagerInvariantsNoError(mock_nested)
def testMultipleArgUnbound(self):
m = mock_contextmanager_generator()
n = mock_contextmanager_generator()
o = mock_contextmanager_generator()
mock_nested = MockNested(m, n, o)
with mock_nested:
self.assertInWithManagerInvariants(m)
self.assertInWithManagerInvariants(n)
self.assertInWithManagerInvariants(o)
self.assertInWithManagerInvariants(mock_nested)
self.assertAfterWithManagerInvariantsNoError(m)
self.assertAfterWithManagerInvariantsNoError(n)
self.assertAfterWithManagerInvariantsNoError(o)
self.assertAfterWithManagerInvariantsNoError(mock_nested)
def testMultipleArgBound(self):
mock_nested = MockNested(mock_contextmanager_generator(),
mock_contextmanager_generator(), mock_contextmanager_generator())
with mock_nested as (m, n, o):
self.assertInWithGeneratorInvariants(m)
self.assertInWithGeneratorInvariants(n)
self.assertInWithGeneratorInvariants(o)
self.assertInWithManagerInvariants(mock_nested)
self.assertAfterWithGeneratorInvariantsNoError(m)
self.assertAfterWithGeneratorInvariantsNoError(n)
self.assertAfterWithGeneratorInvariantsNoError(o)
self.assertAfterWithManagerInvariantsNoError(mock_nested)
class ExceptionalTestCase(unittest.TestCase, ContextmanagerAssertionMixin):
def testSingleResource(self):
cm = mock_contextmanager_generator()
def shouldThrow():
with cm as self.resource:
self.assertInWithManagerInvariants(cm)
self.assertInWithGeneratorInvariants(self.resource)
self.raiseTestException()
self.assertRaises(RuntimeError, shouldThrow)
self.assertAfterWithManagerInvariantsWithError(cm)
self.assertAfterWithGeneratorInvariantsWithError(self.resource)
def testNestedSingleStatements(self):
mock_a = mock_contextmanager_generator()
mock_b = mock_contextmanager_generator()
def shouldThrow():
with mock_a as self.foo:
with mock_b as self.bar:
self.assertInWithManagerInvariants(mock_a)
self.assertInWithManagerInvariants(mock_b)
self.assertInWithGeneratorInvariants(self.foo)
self.assertInWithGeneratorInvariants(self.bar)
self.raiseTestException()
self.assertRaises(RuntimeError, shouldThrow)
self.assertAfterWithManagerInvariantsWithError(mock_a)
self.assertAfterWithManagerInvariantsWithError(mock_b)
self.assertAfterWithGeneratorInvariantsWithError(self.foo)
self.assertAfterWithGeneratorInvariantsWithError(self.bar)
def testMultipleResourcesInSingleStatement(self):
cm_a = mock_contextmanager_generator()
cm_b = mock_contextmanager_generator()
mock_nested = MockNested(cm_a, cm_b)
def shouldThrow():
with mock_nested as (self.resource_a, self.resource_b):
self.assertInWithManagerInvariants(cm_a)
self.assertInWithManagerInvariants(cm_b)
self.assertInWithManagerInvariants(mock_nested)
self.assertInWithGeneratorInvariants(self.resource_a)
self.assertInWithGeneratorInvariants(self.resource_b)
self.raiseTestException()
self.assertRaises(RuntimeError, shouldThrow)
self.assertAfterWithManagerInvariantsWithError(cm_a)
self.assertAfterWithManagerInvariantsWithError(cm_b)
self.assertAfterWithManagerInvariantsWithError(mock_nested)
self.assertAfterWithGeneratorInvariantsWithError(self.resource_a)
self.assertAfterWithGeneratorInvariantsWithError(self.resource_b)
def testNestedExceptionBeforeInnerStatement(self):
mock_a = mock_contextmanager_generator()
mock_b = mock_contextmanager_generator()
self.bar = None
def shouldThrow():
with mock_a as self.foo:
self.assertInWithManagerInvariants(mock_a)
self.assertInWithGeneratorInvariants(self.foo)
self.raiseTestException()
with mock_b as self.bar:
pass
self.assertRaises(RuntimeError, shouldThrow)
self.assertAfterWithManagerInvariantsWithError(mock_a)
self.assertAfterWithGeneratorInvariantsWithError(self.foo)
# The inner statement stuff should never have been touched
self.assertEqual(self.bar, None)
self.assertFalse(mock_b.context_called)
self.assertFalse(mock_b.enter_called)
self.assertFalse(mock_b.exit_called)
self.assertEqual(mock_b.exit_args, None)
def testNestedExceptionAfterInnerStatement(self):
mock_a = mock_contextmanager_generator()
mock_b = mock_contextmanager_generator()
def shouldThrow():
with mock_a as self.foo:
with mock_b as self.bar:
self.assertInWithManagerInvariants(mock_a)
self.assertInWithManagerInvariants(mock_b)
self.assertInWithGeneratorInvariants(self.foo)
self.assertInWithGeneratorInvariants(self.bar)
self.raiseTestException()
self.assertRaises(RuntimeError, shouldThrow)
self.assertAfterWithManagerInvariantsWithError(mock_a)
self.assertAfterWithManagerInvariantsNoError(mock_b)
self.assertAfterWithGeneratorInvariantsWithError(self.foo)
self.assertAfterWithGeneratorInvariantsNoError(self.bar)
class NonLocalFlowControlTestCase(unittest.TestCase):
def testWithBreak(self):
counter = 0
while True:
counter += 1
with mock_contextmanager_generator():
counter += 10
break
counter += 100 # Not reached
self.assertEqual(counter, 11)
def testWithContinue(self):
counter = 0
while True:
counter += 1
if counter > 2:
break
with mock_contextmanager_generator():
counter += 10
continue
counter += 100 # Not reached
self.assertEqual(counter, 12)
def testWithReturn(self):
def foo():
counter = 0
while True:
counter += 1
with mock_contextmanager_generator():
counter += 10
return counter
counter += 100 # Not reached
self.assertEqual(foo(), 11)
def testWithYield(self):
def gen():
with mock_contextmanager_generator():
yield 12
yield 13
x = list(gen())
self.assertEqual(x, [12, 13])
def testWithRaise(self):
counter = 0
try:
counter += 1
with mock_contextmanager_generator():
counter += 10
raise RuntimeError
counter += 100 # Not reached
except RuntimeError:
self.assertEqual(counter, 11)
else:
self.fail("Didn't raise RuntimeError")
class AssignmentTargetTestCase(unittest.TestCase):
def testSingleComplexTarget(self):
targets = {1: [0, 1, 2]}
with mock_contextmanager_generator() as targets[1][0]:
self.assertEqual(targets.keys(), [1])
self.assertEqual(targets[1][0].__class__, MockResource)
with mock_contextmanager_generator() as targets.values()[0][1]:
self.assertEqual(targets.keys(), [1])
self.assertEqual(targets[1][1].__class__, MockResource)
with mock_contextmanager_generator() as targets[2]:
keys = targets.keys()
keys.sort()
self.assertEqual(keys, [1, 2])
class C: pass
blah = C()
with mock_contextmanager_generator() as blah.foo:
self.assertEqual(hasattr(blah, "foo"), True)
def testMultipleComplexTargets(self):
class C:
def __context__(self): return self
def __enter__(self): return 1, 2, 3
def __exit__(self, *a): pass
targets = {1: [0, 1, 2]}
with C() as (targets[1][0], targets[1][1], targets[1][2]):
self.assertEqual(targets, {1: [1, 2, 3]})
with C() as (targets.values()[0][2], targets.values()[0][1], targets.values()[0][0]):
self.assertEqual(targets, {1: [3, 2, 1]})
with C() as (targets[1], targets[2], targets[3]):
self.assertEqual(targets, {1: 1, 2: 2, 3: 3})
class B: pass
blah = B()
with C() as (blah.one, blah.two, blah.three):
self.assertEqual(blah.one, 1)
self.assertEqual(blah.two, 2)
self.assertEqual(blah.three, 3)
def test_main():
run_unittest(FailureTestCase, NonexceptionalTestCase,
NestedNonexceptionalTestCase, ExceptionalTestCase,
NonLocalFlowControlTestCase,
AssignmentTargetTestCase)
if __name__ == '__main__':
test_main()

View file

@ -62,6 +62,7 @@ Dominic Binks
Philippe Biondi Philippe Biondi
Stuart Bishop Stuart Bishop
Roy Bixler Roy Bixler
Mike Bland
Martin Bless Martin Bless
Pablo Bleyer Pablo Bleyer
Erik van Blokland Erik van Blokland

View file

@ -19,6 +19,8 @@ Core and builtins
- dict.__getitem__ now looks for a __missing__ hook before raising - dict.__getitem__ now looks for a __missing__ hook before raising
KeyError. KeyError.
- PEP 343: with statement implemented.
- Fix the encodings package codec search function to only search - Fix the encodings package codec search function to only search
inside its own package. Fixes problem reported in patch #1433198. inside its own package. Fixes problem reported in patch #1433198.

View file

@ -25,6 +25,7 @@ module Python
| For(expr target, expr iter, stmt* body, stmt* orelse) | For(expr target, expr iter, stmt* body, stmt* orelse)
| While(expr test, stmt* body, stmt* orelse) | While(expr test, stmt* body, stmt* orelse)
| If(expr test, stmt* body, stmt* orelse) | If(expr test, stmt* body, stmt* orelse)
| With(expr context_expr, expr? optional_vars, stmt* body)
-- 'type' is a bad name -- 'type' is a bad name
| Raise(expr? type, expr? inst, expr? tback) | Raise(expr? type, expr? inst, expr? tback)

View file

@ -84,6 +84,12 @@ char *If_fields[]={
"body", "body",
"orelse", "orelse",
}; };
PyTypeObject *With_type;
char *With_fields[]={
"context_expr",
"optional_vars",
"body",
};
PyTypeObject *Raise_type; PyTypeObject *Raise_type;
char *Raise_fields[]={ char *Raise_fields[]={
"type", "type",
@ -465,6 +471,8 @@ static int init_types(void)
if (!While_type) return 0; if (!While_type) return 0;
If_type = make_type("If", stmt_type, If_fields, 3); If_type = make_type("If", stmt_type, If_fields, 3);
if (!If_type) return 0; if (!If_type) return 0;
With_type = make_type("With", stmt_type, With_fields, 3);
if (!With_type) return 0;
Raise_type = make_type("Raise", stmt_type, Raise_fields, 3); Raise_type = make_type("Raise", stmt_type, Raise_fields, 3);
if (!Raise_type) return 0; if (!Raise_type) return 0;
TryExcept_type = make_type("TryExcept", stmt_type, TryExcept_fields, 3); TryExcept_type = make_type("TryExcept", stmt_type, TryExcept_fields, 3);
@ -999,6 +1007,29 @@ If(expr_ty test, asdl_seq * body, asdl_seq * orelse, int lineno, PyArena *arena)
return p; return p;
} }
stmt_ty
With(expr_ty context_expr, expr_ty optional_vars, asdl_seq * body, int lineno,
PyArena *arena)
{
stmt_ty p;
if (!context_expr) {
PyErr_SetString(PyExc_ValueError,
"field context_expr is required for With");
return NULL;
}
p = (stmt_ty)PyArena_Malloc(arena, sizeof(*p));
if (!p) {
PyErr_NoMemory();
return NULL;
}
p->kind = With_kind;
p->v.With.context_expr = context_expr;
p->v.With.optional_vars = optional_vars;
p->v.With.body = body;
p->lineno = lineno;
return p;
}
stmt_ty stmt_ty
Raise(expr_ty type, expr_ty inst, expr_ty tback, int lineno, PyArena *arena) Raise(expr_ty type, expr_ty inst, expr_ty tback, int lineno, PyArena *arena)
{ {
@ -2062,6 +2093,26 @@ ast2obj_stmt(void* _o)
goto failed; goto failed;
Py_DECREF(value); Py_DECREF(value);
break; break;
case With_kind:
result = PyType_GenericNew(With_type, NULL, NULL);
if (!result) goto failed;
value = ast2obj_expr(o->v.With.context_expr);
if (!value) goto failed;
if (PyObject_SetAttrString(result, "context_expr", value) == -1)
goto failed;
Py_DECREF(value);
value = ast2obj_expr(o->v.With.optional_vars);
if (!value) goto failed;
if (PyObject_SetAttrString(result, "optional_vars", value) ==
-1)
goto failed;
Py_DECREF(value);
value = ast2obj_list(o->v.With.body, ast2obj_stmt);
if (!value) goto failed;
if (PyObject_SetAttrString(result, "body", value) == -1)
goto failed;
Py_DECREF(value);
break;
case Raise_kind: case Raise_kind:
result = PyType_GenericNew(Raise_type, NULL, NULL); result = PyType_GenericNew(Raise_type, NULL, NULL);
if (!result) goto failed; if (!result) goto failed;
@ -2922,6 +2973,7 @@ init_ast(void)
if(PyDict_SetItemString(d, "For", (PyObject*)For_type) < 0) return; if(PyDict_SetItemString(d, "For", (PyObject*)For_type) < 0) return;
if(PyDict_SetItemString(d, "While", (PyObject*)While_type) < 0) return; if(PyDict_SetItemString(d, "While", (PyObject*)While_type) < 0) return;
if(PyDict_SetItemString(d, "If", (PyObject*)If_type) < 0) return; if(PyDict_SetItemString(d, "If", (PyObject*)If_type) < 0) return;
if(PyDict_SetItemString(d, "With", (PyObject*)With_type) < 0) return;
if(PyDict_SetItemString(d, "Raise", (PyObject*)Raise_type) < 0) return; if(PyDict_SetItemString(d, "Raise", (PyObject*)Raise_type) < 0) return;
if(PyDict_SetItemString(d, "TryExcept", (PyObject*)TryExcept_type) < 0) if(PyDict_SetItemString(d, "TryExcept", (PyObject*)TryExcept_type) < 0)
return; return;

View file

@ -314,7 +314,7 @@ get_operator(const node *n)
} }
} }
/* Set the context ctx for expr_ty e returning 0 on success, -1 on error. /* Set the context ctx for expr_ty e returning 1 on success, 0 on error.
Only sets context for expr kinds that "can appear in assignment context" Only sets context for expr kinds that "can appear in assignment context"
(according to ../Parser/Python.asdl). For other expr kinds, it sets (according to ../Parser/Python.asdl). For other expr kinds, it sets
@ -339,7 +339,7 @@ set_context(expr_ty e, expr_context_ty ctx, const node *n)
a little more complex than necessary as a result. It also means a little more complex than necessary as a result. It also means
that expressions in an augmented assignment have no context. that expressions in an augmented assignment have no context.
Consider restructuring so that augmented assignment uses Consider restructuring so that augmented assignment uses
set_context(), too set_context(), too.
*/ */
assert(ctx != AugStore && ctx != AugLoad); assert(ctx != AugStore && ctx != AugLoad);
@ -2713,6 +2713,46 @@ ast_for_try_stmt(struct compiling *c, const node *n)
return TryFinally(body, finally, LINENO(n), c->c_arena); return TryFinally(body, finally, LINENO(n), c->c_arena);
} }
static expr_ty
ast_for_with_var(struct compiling *c, const node *n)
{
REQ(n, with_var);
if (strcmp(STR(CHILD(n, 0)), "as") != 0) {
ast_error(n, "expected \"with [expr] as [var]\"");
return NULL;
}
return ast_for_expr(c, CHILD(n, 1));
}
/* with_stmt: 'with' test [ with_var ] ':' suite */
static stmt_ty
ast_for_with_stmt(struct compiling *c, const node *n)
{
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));
if (TYPE(CHILD(n, 2)) == with_var) {
optional_vars = ast_for_with_var(c, CHILD(n, 2));
if (!optional_vars) {
return NULL;
}
if (!set_context(optional_vars, Store, n)) {
return NULL;
}
suite_index = 4;
}
suite_seq = ast_for_suite(c, CHILD(n, suite_index));
if (!suite_seq) {
return NULL;
}
return With(context_expr, optional_vars, suite_seq, LINENO(n), c->c_arena);
}
static stmt_ty static stmt_ty
ast_for_classdef(struct compiling *c, const node *n) ast_for_classdef(struct compiling *c, const node *n)
{ {
@ -2813,6 +2853,8 @@ ast_for_stmt(struct compiling *c, const node *n)
return ast_for_for_stmt(c, ch); return ast_for_for_stmt(c, ch);
case try_stmt: case try_stmt:
return ast_for_try_stmt(c, ch); return ast_for_try_stmt(c, ch);
case with_stmt:
return ast_for_with_stmt(c, ch);
case funcdef: case funcdef:
return ast_for_funcdef(c, ch); return ast_for_funcdef(c, ch);
case classdef: case classdef:

View file

@ -97,6 +97,7 @@ static PyObject *load_args(PyObject ***, int);
#define CALL_FLAG_KW 2 #define CALL_FLAG_KW 2
#ifdef LLTRACE #ifdef LLTRACE
static int lltrace;
static int prtrace(PyObject *, char *); static int prtrace(PyObject *, char *);
#endif #endif
static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *, static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *,
@ -540,9 +541,6 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
unsigned char *first_instr; unsigned char *first_instr;
PyObject *names; PyObject *names;
PyObject *consts; PyObject *consts;
#ifdef LLTRACE
int lltrace;
#endif
#if defined(Py_DEBUG) || defined(LLTRACE) #if defined(Py_DEBUG) || defined(LLTRACE)
/* Make it easier to find out where we are with a debugger */ /* Make it easier to find out where we are with a debugger */
char *filename; char *filename;
@ -661,10 +659,12 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
#define STACKADJ(n) { (void)(BASIC_STACKADJ(n), \ #define STACKADJ(n) { (void)(BASIC_STACKADJ(n), \
lltrace && prtrace(TOP(), "stackadj")); \ lltrace && prtrace(TOP(), "stackadj")); \
assert(STACK_LEVEL() <= f->f_stacksize); } assert(STACK_LEVEL() <= f->f_stacksize); }
#define EXT_POP(STACK_POINTER) (lltrace && prtrace(*(STACK_POINTER), "ext_pop"), *--(STACK_POINTER))
#else #else
#define PUSH(v) BASIC_PUSH(v) #define PUSH(v) BASIC_PUSH(v)
#define POP() BASIC_POP() #define POP() BASIC_POP()
#define STACKADJ(n) BASIC_STACKADJ(n) #define STACKADJ(n) BASIC_STACKADJ(n)
#define EXT_POP(STACK_POINTER) (*--(STACK_POINTER))
#endif #endif
/* Local variable macros */ /* Local variable macros */
@ -2172,6 +2172,43 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
STACK_LEVEL()); STACK_LEVEL());
continue; continue;
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 or WHY_CONTINUE), retval
- SECOND = WHY_*; no retval below it
- (SECOND, THIRD, FOURTH) = exc_info()
In the last case, we must call
TOP(SECOND, THIRD, FOURTH)
otherwise we must call
TOP(None, None, None)
but we must preserve the stack entries below TOP.
The code here just sets the stack up for the call;
separate CALL_FUNCTION(3) and POP_TOP opcodes are
emitted by the compiler.
*/
x = TOP();
u = SECOND();
if (PyInt_Check(u) || u == Py_None) {
u = v = w = Py_None;
}
else {
v = THIRD();
w = FOURTH();
}
Py_INCREF(u);
Py_INCREF(v);
Py_INCREF(w);
PUSH(u);
PUSH(v);
PUSH(w);
break;
}
case CALL_FUNCTION: case CALL_FUNCTION:
{ {
PyObject **sp; PyObject **sp;
@ -2511,9 +2548,9 @@ fast_yield:
return retval; return retval;
} }
/* this is gonna seem *real weird*, but if you put some other code between /* This is gonna seem *real weird*, but if you put some other code between
PyEval_EvalFrame() and PyEval_EvalCodeEx() you will need to adjust PyEval_EvalFrame() and PyEval_EvalCodeEx() you will need to adjust
the test in the if statement in Misc/gdbinit:pystack* */ the test in the if statements in Misc/gdbinit (pystack and pystackv). */
PyObject * PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals, PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
@ -3473,8 +3510,6 @@ PyEval_GetFuncDesc(PyObject *func)
} }
} }
#define EXT_POP(STACK_POINTER) (*--(STACK_POINTER))
static void static void
err_args(PyObject *func, int flags, int nargs) err_args(PyObject *func, int flags, int nargs)
{ {

View file

@ -191,6 +191,8 @@ static void compiler_pop_fblock(struct compiler *, enum fblocktype,
static int inplace_binop(struct compiler *, operator_ty); static int inplace_binop(struct compiler *, operator_ty);
static int expr_constant(expr_ty e); static int expr_constant(expr_ty e);
static int compiler_with(struct compiler *, stmt_ty);
static PyCodeObject *assemble(struct compiler *, int addNone); static PyCodeObject *assemble(struct compiler *, int addNone);
static PyObject *__doc__; static PyObject *__doc__;
@ -289,6 +291,7 @@ PyAST_Compile(mod_ty mod, const char *filename, PyCompilerFlags *flags,
error: error:
compiler_free(&c); compiler_free(&c);
assert(!PyErr_Occurred());
return co; return co;
} }
@ -1157,6 +1160,18 @@ compiler_exit_scope(struct compiler *c)
} }
/* Allocate a new "anonymous" local variable.
Used by list comprehensions and with statements.
*/
static PyObject *
compiler_new_tmpname(struct compiler *c)
{
char tmpname[256];
PyOS_snprintf(tmpname, sizeof(tmpname), "_[%d]", ++c->u->u_tmpname);
return PyString_FromString(tmpname);
}
/* Allocate a new block and return a pointer to it. /* Allocate a new block and return a pointer to it.
Returns NULL on error. Returns NULL on error.
*/ */
@ -1360,7 +1375,8 @@ opcode_stack_effect(int opcode, int oparg)
return -1; return -1;
case BREAK_LOOP: case BREAK_LOOP:
return 0; return 0;
case WITH_CLEANUP:
return 3;
case LOAD_LOCALS: case LOAD_LOCALS:
return 1; return 1;
case RETURN_VALUE: case RETURN_VALUE:
@ -2663,6 +2679,8 @@ compiler_visit_stmt(struct compiler *c, stmt_ty s)
break; break;
case Continue_kind: case Continue_kind:
return compiler_continue(c); return compiler_continue(c);
case With_kind:
return compiler_with(c, s);
} }
return 1; return 1;
} }
@ -3124,7 +3142,6 @@ compiler_listcomp_generator(struct compiler *c, PyObject *tmpname,
static int static int
compiler_listcomp(struct compiler *c, expr_ty e) compiler_listcomp(struct compiler *c, expr_ty e)
{ {
char tmpname[256];
identifier tmp; identifier tmp;
int rc = 0; int rc = 0;
static identifier append; static identifier append;
@ -3136,8 +3153,7 @@ compiler_listcomp(struct compiler *c, expr_ty e)
if (!append) if (!append)
return 0; return 0;
} }
PyOS_snprintf(tmpname, sizeof(tmpname), "_[%d]", ++c->u->u_tmpname); tmp = compiler_new_tmpname(c);
tmp = PyString_FromString(tmpname);
if (!tmp) if (!tmp)
return 0; return 0;
ADDOP_I(c, BUILD_LIST, 0); ADDOP_I(c, BUILD_LIST, 0);
@ -3291,6 +3307,148 @@ expr_constant(expr_ty e)
} }
} }
/*
Implements the with statement from PEP 343.
The semantics outlined in that PEP are as follows:
with EXPR as VAR:
BLOCK
It is implemented roughly as:
context = (EXPR).__context__()
exit = context.__exit__ # not calling it
value = context.__enter__()
try:
VAR = value # if VAR present in the syntax
BLOCK
finally:
if an exception was raised:
exc = copy of (exception, instance, traceback)
else:
exc = (None, None, None)
exit(*exc)
*/
static int
compiler_with(struct compiler *c, stmt_ty s)
{
static identifier context_attr, enter_attr, exit_attr;
basicblock *block, *finally;
identifier tmpexit, tmpvalue = NULL;
assert(s->kind == With_kind);
if (!context_attr) {
context_attr = PyString_InternFromString("__context__");
if (!context_attr)
return 0;
}
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;
/* 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
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).__context__() */
VISIT(c, expr, s->v.With.context_expr);
ADDOP_O(c, LOAD_ATTR, context_attr, names);
ADDOP_I(c, CALL_FUNCTION, 0);
/* Squirrel away context.__exit__ */
ADDOP(c, DUP_TOP);
ADDOP_O(c, LOAD_ATTR, exit_attr, names);
if (!compiler_nameop(c, tmpexit, Store))
return 0;
/* 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);
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);
}
/* BLOCK code */
VISIT_SEQ(c, stmt, s->v.With.body);
/* End of try block; start the finally block */
ADDOP(c, POP_BLOCK);
compiler_pop_fblock(c, FINALLY_TRY, block);
ADDOP_O(c, LOAD_CONST, Py_None, consts);
compiler_use_next_block(c, finally);
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;
ADDOP(c, WITH_CLEANUP);
ADDOP_I(c, CALL_FUNCTION, 3);
ADDOP(c, POP_TOP);
/* Finally block ends. */
ADDOP(c, END_FINALLY);
compiler_pop_fblock(c, FINALLY_END, finally);
return 1;
}
static int static int
compiler_visit_expr(struct compiler *c, expr_ty e) compiler_visit_expr(struct compiler *c, expr_ty e)
{ {

File diff suppressed because it is too large Load diff

View file

@ -54,9 +54,10 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *);
Python 2.4b1: 62061 Python 2.4b1: 62061
Python 2.5a0: 62071 Python 2.5a0: 62071
Python 2.5a0: 62081 (ast-branch) Python 2.5a0: 62081 (ast-branch)
Python 2.5a0: 62091 (with)
. .
*/ */
#define MAGIC (62081 | ((long)'\r'<<16) | ((long)'\n'<<24)) #define MAGIC (62091 | ((long)'\r'<<16) | ((long)'\n'<<24))
/* Magic word as global; note that _PyImport_Init() can change the /* Magic word as global; note that _PyImport_Init() can change the
value of this global to accommodate for alterations of how the value of this global to accommodate for alterations of how the

View file

@ -890,6 +890,21 @@ error:
} \ } \
} }
static int
symtable_new_tmpname(struct symtable *st)
{
char tmpname[256];
identifier tmp;
PyOS_snprintf(tmpname, sizeof(tmpname), "_[%d]",
++st->st_cur->ste_tmpname);
tmp = PyString_InternFromString(tmpname);
if (!symtable_add_def(st, tmp, DEF_LOCAL))
return 0;
Py_DECREF(tmp);
return 1;
}
static int static int
symtable_visit_stmt(struct symtable *st, stmt_ty s) symtable_visit_stmt(struct symtable *st, stmt_ty s)
{ {
@ -1051,6 +1066,17 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
case Continue_kind: case Continue_kind:
/* nothing to do here */ /* nothing to do here */
break; break;
case With_kind:
if (!symtable_new_tmpname(st))
return 0;
VISIT(st, expr, s->v.With.context_expr);
if (s->v.With.optional_vars) {
if (!symtable_new_tmpname(st))
return 0;
VISIT(st, expr, s->v.With.optional_vars);
}
VISIT_SEQ(st, stmt, s->v.With.body);
break;
} }
return 1; return 1;
} }
@ -1093,26 +1119,16 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
VISIT_SEQ(st, expr, e->v.Dict.keys); VISIT_SEQ(st, expr, e->v.Dict.keys);
VISIT_SEQ(st, expr, e->v.Dict.values); VISIT_SEQ(st, expr, e->v.Dict.values);
break; break;
case ListComp_kind: { case ListComp_kind:
char tmpname[256]; if (!symtable_new_tmpname(st))
identifier tmp;
PyOS_snprintf(tmpname, sizeof(tmpname), "_[%d]",
++st->st_cur->ste_tmpname);
tmp = PyString_InternFromString(tmpname);
if (!symtable_add_def(st, tmp, DEF_LOCAL))
return 0; return 0;
Py_DECREF(tmp);
VISIT(st, expr, e->v.ListComp.elt); VISIT(st, expr, e->v.ListComp.elt);
VISIT_SEQ(st, comprehension, e->v.ListComp.generators); VISIT_SEQ(st, comprehension, e->v.ListComp.generators);
break; break;
} case GeneratorExp_kind:
case GeneratorExp_kind: { if (!symtable_visit_genexp(st, e))
if (!symtable_visit_genexp(st, e)) {
return 0; return 0;
}
break; break;
}
case Yield_kind: case Yield_kind:
if (e->v.Yield.value) if (e->v.Yield.value)
VISIT(st, expr, e->v.Yield.value); VISIT(st, expr, e->v.Yield.value);

View file

@ -20,6 +20,7 @@ Break:
Continue: Continue:
For: assign, list, body, else_& For: assign, list, body, else_&
While: test, body, else_& While: test, body, else_&
With: expr, vars&, body
If: tests!, else_& If: tests!, else_&
Exec: expr, locals&, globals& Exec: expr, locals&, globals&
From: modname*, names* From: modname*, names*
@ -42,7 +43,7 @@ AssAttr: expr, attrname*, flags*
ListComp: expr, quals! ListComp: expr, quals!
ListCompFor: assign, list, ifs! ListCompFor: assign, list, ifs!
ListCompIf: test ListCompIf: test
GenExpr: code GenExpr: code
GenExprInner: expr, quals! GenExprInner: expr, quals!
GenExprFor: assign, iter, ifs! GenExprFor: assign, iter, ifs!
GenExprIf: test GenExprIf: test