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:
parent
5fec904f84
commit
c2e20744b2
|
@ -272,6 +272,11 @@
|
|||
\lineiii{}{\member{else_}}{}
|
||||
\hline
|
||||
|
||||
\lineiii{With}{\member{expr}}{}
|
||||
\lineiii{}{\member{vars&}}{}
|
||||
\lineiii{}{\member{body}}{}
|
||||
\hline
|
||||
|
||||
\lineiii{Yield}{\member{value}}{}
|
||||
\hline
|
||||
|
||||
|
|
|
@ -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}.
|
||||
|
||||
|
||||
\section{The \keyword{with} statement\label{with}}
|
||||
\stindex{with}
|
||||
|
||||
The \keyword{with} statement specifies
|
||||
|
||||
|
||||
\section{Function definitions\label{function}}
|
||||
\indexii{function}{definition}
|
||||
\stindex{def}
|
||||
|
|
|
@ -70,7 +70,7 @@ global_stmt: 'global' NAME (',' NAME)*
|
|||
exec_stmt: 'exec' expr ['in' 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]
|
||||
while_stmt: 'while' test ':' suite ['else' ':' suite]
|
||||
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
|
||||
|
@ -79,6 +79,8 @@ try_stmt: ('try' ':' suite
|
|||
['else' ':' 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
|
||||
except_clause: 'except' [test [',' test]]
|
||||
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
|
||||
|
|
|
@ -61,11 +61,11 @@ struct _mod {
|
|||
struct _stmt {
|
||||
enum { FunctionDef_kind=1, ClassDef_kind=2, Return_kind=3,
|
||||
Delete_kind=4, Assign_kind=5, AugAssign_kind=6, Print_kind=7,
|
||||
For_kind=8, While_kind=9, If_kind=10, Raise_kind=11,
|
||||
TryExcept_kind=12, TryFinally_kind=13, Assert_kind=14,
|
||||
Import_kind=15, ImportFrom_kind=16, Exec_kind=17,
|
||||
Global_kind=18, Expr_kind=19, Pass_kind=20, Break_kind=21,
|
||||
Continue_kind=22 } kind;
|
||||
For_kind=8, While_kind=9, If_kind=10, With_kind=11,
|
||||
Raise_kind=12, TryExcept_kind=13, TryFinally_kind=14,
|
||||
Assert_kind=15, Import_kind=16, ImportFrom_kind=17,
|
||||
Exec_kind=18, Global_kind=19, Expr_kind=20, Pass_kind=21,
|
||||
Break_kind=22, Continue_kind=23 } kind;
|
||||
union {
|
||||
struct {
|
||||
identifier name;
|
||||
|
@ -124,6 +124,12 @@ struct _stmt {
|
|||
asdl_seq *orelse;
|
||||
} If;
|
||||
|
||||
struct {
|
||||
expr_ty context_expr;
|
||||
expr_ty optional_vars;
|
||||
asdl_seq *body;
|
||||
} With;
|
||||
|
||||
struct {
|
||||
expr_ty type;
|
||||
expr_ty inst;
|
||||
|
@ -355,6 +361,8 @@ stmt_ty While(expr_ty test, asdl_seq * body, asdl_seq * orelse, int lineno,
|
|||
PyArena *arena);
|
||||
stmt_ty If(expr_ty test, asdl_seq * body, asdl_seq * orelse, int lineno,
|
||||
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
|
||||
*arena);
|
||||
stmt_ty TryExcept(asdl_seq * body, asdl_seq * handlers, asdl_seq * orelse, int
|
||||
|
|
|
@ -38,45 +38,47 @@
|
|||
#define while_stmt 293
|
||||
#define for_stmt 294
|
||||
#define try_stmt 295
|
||||
#define except_clause 296
|
||||
#define suite 297
|
||||
#define testlist_safe 298
|
||||
#define old_test 299
|
||||
#define old_lambdef 300
|
||||
#define test 301
|
||||
#define or_test 302
|
||||
#define and_test 303
|
||||
#define not_test 304
|
||||
#define comparison 305
|
||||
#define comp_op 306
|
||||
#define expr 307
|
||||
#define xor_expr 308
|
||||
#define and_expr 309
|
||||
#define shift_expr 310
|
||||
#define arith_expr 311
|
||||
#define term 312
|
||||
#define factor 313
|
||||
#define power 314
|
||||
#define atom 315
|
||||
#define listmaker 316
|
||||
#define testlist_gexp 317
|
||||
#define lambdef 318
|
||||
#define trailer 319
|
||||
#define subscriptlist 320
|
||||
#define subscript 321
|
||||
#define sliceop 322
|
||||
#define exprlist 323
|
||||
#define testlist 324
|
||||
#define dictmaker 325
|
||||
#define classdef 326
|
||||
#define arglist 327
|
||||
#define argument 328
|
||||
#define list_iter 329
|
||||
#define list_for 330
|
||||
#define list_if 331
|
||||
#define gen_iter 332
|
||||
#define gen_for 333
|
||||
#define gen_if 334
|
||||
#define testlist1 335
|
||||
#define encoding_decl 336
|
||||
#define yield_expr 337
|
||||
#define with_stmt 296
|
||||
#define with_var 297
|
||||
#define except_clause 298
|
||||
#define suite 299
|
||||
#define testlist_safe 300
|
||||
#define old_test 301
|
||||
#define old_lambdef 302
|
||||
#define test 303
|
||||
#define or_test 304
|
||||
#define and_test 305
|
||||
#define not_test 306
|
||||
#define comparison 307
|
||||
#define comp_op 308
|
||||
#define expr 309
|
||||
#define xor_expr 310
|
||||
#define and_expr 311
|
||||
#define shift_expr 312
|
||||
#define arith_expr 313
|
||||
#define term 314
|
||||
#define factor 315
|
||||
#define power 316
|
||||
#define atom 317
|
||||
#define listmaker 318
|
||||
#define testlist_gexp 319
|
||||
#define lambdef 320
|
||||
#define trailer 321
|
||||
#define subscriptlist 322
|
||||
#define subscript 323
|
||||
#define sliceop 324
|
||||
#define exprlist 325
|
||||
#define testlist 326
|
||||
#define dictmaker 327
|
||||
#define classdef 328
|
||||
#define arglist 329
|
||||
#define argument 330
|
||||
#define list_iter 331
|
||||
#define list_for 332
|
||||
#define list_if 333
|
||||
#define gen_iter 334
|
||||
#define gen_for 335
|
||||
#define gen_if 336
|
||||
#define testlist1 337
|
||||
#define encoding_decl 338
|
||||
#define yield_expr 339
|
||||
|
|
|
@ -72,13 +72,12 @@ extern "C" {
|
|||
#define INPLACE_XOR 78
|
||||
#define INPLACE_OR 79
|
||||
#define BREAK_LOOP 80
|
||||
|
||||
#define WITH_CLEANUP 81
|
||||
#define LOAD_LOCALS 82
|
||||
#define RETURN_VALUE 83
|
||||
#define IMPORT_STAR 84
|
||||
#define EXEC_STMT 85
|
||||
#define YIELD_VALUE 86
|
||||
|
||||
#define POP_BLOCK 87
|
||||
#define END_FINALLY 88
|
||||
#define BUILD_CLASS 89
|
||||
|
|
|
@ -1297,6 +1297,31 @@ class While(Node):
|
|||
def __repr__(self):
|
||||
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):
|
||||
def __init__(self, value, lineno=None):
|
||||
self.value = value
|
||||
|
|
|
@ -41,6 +41,7 @@ def jabs_op(name, op):
|
|||
hasjabs.append(op)
|
||||
|
||||
# Instruction opcodes for compiled code
|
||||
# Blank lines correspond to available opcodes
|
||||
|
||||
def_op('STOP_CODE', 0)
|
||||
def_op('POP_TOP', 1)
|
||||
|
@ -59,7 +60,6 @@ def_op('UNARY_INVERT', 15)
|
|||
|
||||
def_op('LIST_APPEND', 18)
|
||||
def_op('BINARY_POWER', 19)
|
||||
|
||||
def_op('BINARY_MULTIPLY', 20)
|
||||
def_op('BINARY_DIVIDE', 21)
|
||||
def_op('BINARY_MODULO', 22)
|
||||
|
@ -70,7 +70,6 @@ def_op('BINARY_FLOOR_DIVIDE', 26)
|
|||
def_op('BINARY_TRUE_DIVIDE', 27)
|
||||
def_op('INPLACE_FLOOR_DIVIDE', 28)
|
||||
def_op('INPLACE_TRUE_DIVIDE', 29)
|
||||
|
||||
def_op('SLICE+0', 30)
|
||||
def_op('SLICE+1', 31)
|
||||
def_op('SLICE+2', 32)
|
||||
|
@ -93,7 +92,6 @@ def_op('INPLACE_DIVIDE', 58)
|
|||
def_op('INPLACE_MODULO', 59)
|
||||
def_op('STORE_SUBSCR', 60)
|
||||
def_op('DELETE_SUBSCR', 61)
|
||||
|
||||
def_op('BINARY_LSHIFT', 62)
|
||||
def_op('BINARY_RSHIFT', 63)
|
||||
def_op('BINARY_AND', 64)
|
||||
|
@ -113,13 +111,12 @@ def_op('INPLACE_AND', 77)
|
|||
def_op('INPLACE_XOR', 78)
|
||||
def_op('INPLACE_OR', 79)
|
||||
def_op('BREAK_LOOP', 80)
|
||||
|
||||
def_op('WITH_CLEANUP', 81)
|
||||
def_op('LOAD_LOCALS', 82)
|
||||
def_op('RETURN_VALUE', 83)
|
||||
def_op('IMPORT_STAR', 84)
|
||||
def_op('EXEC_STMT', 85)
|
||||
def_op('YIELD_VALUE', 86)
|
||||
|
||||
def_op('POP_BLOCK', 87)
|
||||
def_op('END_FINALLY', 88)
|
||||
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('MAKE_FUNCTION', 132) # Number of args with default values
|
||||
def_op('BUILD_SLICE', 133) # Number of items
|
||||
|
||||
def_op('MAKE_CLOSURE', 134)
|
||||
def_op('LOAD_CLOSURE', 135)
|
||||
hasfree.append(135)
|
||||
|
@ -183,7 +179,6 @@ 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
|
||||
|
||||
|
|
|
@ -50,48 +50,50 @@ if_stmt = 292
|
|||
while_stmt = 293
|
||||
for_stmt = 294
|
||||
try_stmt = 295
|
||||
except_clause = 296
|
||||
suite = 297
|
||||
testlist_safe = 298
|
||||
old_test = 299
|
||||
old_lambdef = 300
|
||||
test = 301
|
||||
or_test = 302
|
||||
and_test = 303
|
||||
not_test = 304
|
||||
comparison = 305
|
||||
comp_op = 306
|
||||
expr = 307
|
||||
xor_expr = 308
|
||||
and_expr = 309
|
||||
shift_expr = 310
|
||||
arith_expr = 311
|
||||
term = 312
|
||||
factor = 313
|
||||
power = 314
|
||||
atom = 315
|
||||
listmaker = 316
|
||||
testlist_gexp = 317
|
||||
lambdef = 318
|
||||
trailer = 319
|
||||
subscriptlist = 320
|
||||
subscript = 321
|
||||
sliceop = 322
|
||||
exprlist = 323
|
||||
testlist = 324
|
||||
dictmaker = 325
|
||||
classdef = 326
|
||||
arglist = 327
|
||||
argument = 328
|
||||
list_iter = 329
|
||||
list_for = 330
|
||||
list_if = 331
|
||||
gen_iter = 332
|
||||
gen_for = 333
|
||||
gen_if = 334
|
||||
testlist1 = 335
|
||||
encoding_decl = 336
|
||||
yield_expr = 337
|
||||
with_stmt = 296
|
||||
with_var = 297
|
||||
except_clause = 298
|
||||
suite = 299
|
||||
testlist_safe = 300
|
||||
old_test = 301
|
||||
old_lambdef = 302
|
||||
test = 303
|
||||
or_test = 304
|
||||
and_test = 305
|
||||
not_test = 306
|
||||
comparison = 307
|
||||
comp_op = 308
|
||||
expr = 309
|
||||
xor_expr = 310
|
||||
and_expr = 311
|
||||
shift_expr = 312
|
||||
arith_expr = 313
|
||||
term = 314
|
||||
factor = 315
|
||||
power = 316
|
||||
atom = 317
|
||||
listmaker = 318
|
||||
testlist_gexp = 319
|
||||
lambdef = 320
|
||||
trailer = 321
|
||||
subscriptlist = 322
|
||||
subscript = 323
|
||||
sliceop = 324
|
||||
exprlist = 325
|
||||
testlist = 326
|
||||
dictmaker = 327
|
||||
classdef = 328
|
||||
arglist = 329
|
||||
argument = 330
|
||||
list_iter = 331
|
||||
list_for = 332
|
||||
list_if = 333
|
||||
gen_iter = 334
|
||||
gen_for = 335
|
||||
gen_if = 336
|
||||
testlist1 = 337
|
||||
encoding_decl = 338
|
||||
yield_expr = 339
|
||||
#--end constants--
|
||||
|
||||
sym_name = {}
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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]
|
||||
|
|
@ -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()
|
|
@ -62,6 +62,7 @@ Dominic Binks
|
|||
Philippe Biondi
|
||||
Stuart Bishop
|
||||
Roy Bixler
|
||||
Mike Bland
|
||||
Martin Bless
|
||||
Pablo Bleyer
|
||||
Erik van Blokland
|
||||
|
|
|
@ -19,6 +19,8 @@ Core and builtins
|
|||
- dict.__getitem__ now looks for a __missing__ hook before raising
|
||||
KeyError.
|
||||
|
||||
- PEP 343: with statement implemented.
|
||||
|
||||
- Fix the encodings package codec search function to only search
|
||||
inside its own package. Fixes problem reported in patch #1433198.
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ module Python
|
|||
| For(expr target, expr iter, stmt* body, stmt* orelse)
|
||||
| While(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
|
||||
| Raise(expr? type, expr? inst, expr? tback)
|
||||
|
|
|
@ -84,6 +84,12 @@ char *If_fields[]={
|
|||
"body",
|
||||
"orelse",
|
||||
};
|
||||
PyTypeObject *With_type;
|
||||
char *With_fields[]={
|
||||
"context_expr",
|
||||
"optional_vars",
|
||||
"body",
|
||||
};
|
||||
PyTypeObject *Raise_type;
|
||||
char *Raise_fields[]={
|
||||
"type",
|
||||
|
@ -465,6 +471,8 @@ static int init_types(void)
|
|||
if (!While_type) return 0;
|
||||
If_type = make_type("If", stmt_type, If_fields, 3);
|
||||
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);
|
||||
if (!Raise_type) return 0;
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
Raise(expr_ty type, expr_ty inst, expr_ty tback, int lineno, PyArena *arena)
|
||||
{
|
||||
|
@ -2062,6 +2093,26 @@ ast2obj_stmt(void* _o)
|
|||
goto failed;
|
||||
Py_DECREF(value);
|
||||
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:
|
||||
result = PyType_GenericNew(Raise_type, NULL, NULL);
|
||||
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, "While", (PyObject*)While_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, "TryExcept", (PyObject*)TryExcept_type) < 0)
|
||||
return;
|
||||
|
|
46
Python/ast.c
46
Python/ast.c
|
@ -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"
|
||||
(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
|
||||
that expressions in an augmented assignment have no context.
|
||||
Consider restructuring so that augmented assignment uses
|
||||
set_context(), too
|
||||
set_context(), too.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
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);
|
||||
case try_stmt:
|
||||
return ast_for_try_stmt(c, ch);
|
||||
case with_stmt:
|
||||
return ast_for_with_stmt(c, ch);
|
||||
case funcdef:
|
||||
return ast_for_funcdef(c, ch);
|
||||
case classdef:
|
||||
|
|
|
@ -97,6 +97,7 @@ static PyObject *load_args(PyObject ***, int);
|
|||
#define CALL_FLAG_KW 2
|
||||
|
||||
#ifdef LLTRACE
|
||||
static int lltrace;
|
||||
static int prtrace(PyObject *, char *);
|
||||
#endif
|
||||
static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *,
|
||||
|
@ -540,9 +541,6 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
|
|||
unsigned char *first_instr;
|
||||
PyObject *names;
|
||||
PyObject *consts;
|
||||
#ifdef LLTRACE
|
||||
int lltrace;
|
||||
#endif
|
||||
#if defined(Py_DEBUG) || defined(LLTRACE)
|
||||
/* Make it easier to find out where we are with a debugger */
|
||||
char *filename;
|
||||
|
@ -661,10 +659,12 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
|
|||
#define STACKADJ(n) { (void)(BASIC_STACKADJ(n), \
|
||||
lltrace && prtrace(TOP(), "stackadj")); \
|
||||
assert(STACK_LEVEL() <= f->f_stacksize); }
|
||||
#define EXT_POP(STACK_POINTER) (lltrace && prtrace(*(STACK_POINTER), "ext_pop"), *--(STACK_POINTER))
|
||||
#else
|
||||
#define PUSH(v) BASIC_PUSH(v)
|
||||
#define POP() BASIC_POP()
|
||||
#define STACKADJ(n) BASIC_STACKADJ(n)
|
||||
#define EXT_POP(STACK_POINTER) (*--(STACK_POINTER))
|
||||
#endif
|
||||
|
||||
/* Local variable macros */
|
||||
|
@ -2172,6 +2172,43 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throw)
|
|||
STACK_LEVEL());
|
||||
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:
|
||||
{
|
||||
PyObject **sp;
|
||||
|
@ -2511,9 +2548,9 @@ fast_yield:
|
|||
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
|
||||
the test in the if statement in Misc/gdbinit:pystack* */
|
||||
the test in the if statements in Misc/gdbinit (pystack and pystackv). */
|
||||
|
||||
PyObject *
|
||||
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
|
||||
err_args(PyObject *func, int flags, int nargs)
|
||||
{
|
||||
|
|
166
Python/compile.c
166
Python/compile.c
|
@ -191,6 +191,8 @@ static void compiler_pop_fblock(struct compiler *, enum fblocktype,
|
|||
static int inplace_binop(struct compiler *, operator_ty);
|
||||
static int expr_constant(expr_ty e);
|
||||
|
||||
static int compiler_with(struct compiler *, stmt_ty);
|
||||
|
||||
static PyCodeObject *assemble(struct compiler *, int addNone);
|
||||
static PyObject *__doc__;
|
||||
|
||||
|
@ -289,6 +291,7 @@ PyAST_Compile(mod_ty mod, const char *filename, PyCompilerFlags *flags,
|
|||
|
||||
error:
|
||||
compiler_free(&c);
|
||||
assert(!PyErr_Occurred());
|
||||
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.
|
||||
Returns NULL on error.
|
||||
*/
|
||||
|
@ -1360,7 +1375,8 @@ opcode_stack_effect(int opcode, int oparg)
|
|||
return -1;
|
||||
case BREAK_LOOP:
|
||||
return 0;
|
||||
|
||||
case WITH_CLEANUP:
|
||||
return 3;
|
||||
case LOAD_LOCALS:
|
||||
return 1;
|
||||
case RETURN_VALUE:
|
||||
|
@ -2663,6 +2679,8 @@ compiler_visit_stmt(struct compiler *c, stmt_ty s)
|
|||
break;
|
||||
case Continue_kind:
|
||||
return compiler_continue(c);
|
||||
case With_kind:
|
||||
return compiler_with(c, s);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
@ -3124,7 +3142,6 @@ compiler_listcomp_generator(struct compiler *c, PyObject *tmpname,
|
|||
static int
|
||||
compiler_listcomp(struct compiler *c, expr_ty e)
|
||||
{
|
||||
char tmpname[256];
|
||||
identifier tmp;
|
||||
int rc = 0;
|
||||
static identifier append;
|
||||
|
@ -3136,8 +3153,7 @@ compiler_listcomp(struct compiler *c, expr_ty e)
|
|||
if (!append)
|
||||
return 0;
|
||||
}
|
||||
PyOS_snprintf(tmpname, sizeof(tmpname), "_[%d]", ++c->u->u_tmpname);
|
||||
tmp = PyString_FromString(tmpname);
|
||||
tmp = compiler_new_tmpname(c);
|
||||
if (!tmp)
|
||||
return 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
|
||||
compiler_visit_expr(struct compiler *c, expr_ty e)
|
||||
{
|
||||
|
|
1419
Python/graminit.c
1419
Python/graminit.c
File diff suppressed because it is too large
Load Diff
|
@ -54,9 +54,10 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *);
|
|||
Python 2.4b1: 62061
|
||||
Python 2.5a0: 62071
|
||||
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
|
||||
value of this global to accommodate for alterations of how the
|
||||
|
|
|
@ -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
|
||||
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:
|
||||
/* nothing to do here */
|
||||
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;
|
||||
}
|
||||
|
@ -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.values);
|
||||
break;
|
||||
case ListComp_kind: {
|
||||
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))
|
||||
case ListComp_kind:
|
||||
if (!symtable_new_tmpname(st))
|
||||
return 0;
|
||||
Py_DECREF(tmp);
|
||||
VISIT(st, expr, e->v.ListComp.elt);
|
||||
VISIT_SEQ(st, comprehension, e->v.ListComp.generators);
|
||||
break;
|
||||
}
|
||||
case GeneratorExp_kind: {
|
||||
if (!symtable_visit_genexp(st, e)) {
|
||||
case GeneratorExp_kind:
|
||||
if (!symtable_visit_genexp(st, e))
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Yield_kind:
|
||||
if (e->v.Yield.value)
|
||||
VISIT(st, expr, e->v.Yield.value);
|
||||
|
|
|
@ -20,6 +20,7 @@ Break:
|
|||
Continue:
|
||||
For: assign, list, body, else_&
|
||||
While: test, body, else_&
|
||||
With: expr, vars&, body
|
||||
If: tests!, else_&
|
||||
Exec: expr, locals&, globals&
|
||||
From: modname*, names*
|
||||
|
|
Loading…
Reference in New Issue