Don't be wishy-washy about the call to __exit__().

Fix the redirecting_stdout() example (remove the try/finally).
This commit is contained in:
Guido van Rossum 2005-05-14 02:02:40 +00:00
parent 3e348d5237
commit e9086b2485
1 changed files with 59 additions and 56 deletions

View File

@ -46,23 +46,28 @@ Specification
The translation of the above statement is: The translation of the above statement is:
abc = EXPR abc = EXPR
exc = () # Or (None, None, None) ?
try: try:
VAR = abc.__enter__() try:
BLOCK VAR = abc.__enter__()
BLOCK
except:
exc = sys.exc_info()
raise
finally: finally:
abc.__exit__(*sys.exc_info()) # XXX See below abc.__exit__(exc)
If the "as VAR" part of the syntax is omitted, the "VAR =" part of If the "as VAR" part of the syntax is omitted, the "VAR =" part of
the translation is omitted (but abc.__enter__() is still called). the translation is omitted (but abc.__enter__() is still called).
The call to abc.__exit__() is only approximated as written. The The calling convention for abc.__exit__() is: as follows. If the
actual calling convention is: If the finally-suite was reached finally-suite was reached through normal completion of BLOCK or
through normal completion of BLOCK or through a "non-local goto" through a "non-local goto" (a break, continue or return statement
(a break, continue or return statement in BLOCK), abc.__exit__() in BLOCK), abc.__exit__() is called without arguments (or perhaps
is called without arguments (or perhaps with three None with three None arguments?). If the finally-suite was reached
arguments). If the finally-suite was reached through an exception through an exception raised in BLOCK, abc.__exit__() is called
raised in BLOCK, abc.__exit__() is called with three arguments with three arguments representing the exception type, value, and
representing the exception type, value, and traceback. traceback.
Optional Generator Decorator Optional Generator Decorator
@ -70,41 +75,41 @@ Optional Generator Decorator
a generator that yields exactly once to control a do-statement. a generator that yields exactly once to control a do-statement.
Here's a sketch of such a decorator: Here's a sketch of such a decorator:
class Wrapper(object): class Wrapper(object):
def __init__(self, gen): def __init__(self, gen):
self.gen = gen self.gen = gen
self.state = "initial" self.state = "initial"
def __enter__(self): def __enter__(self):
assert self.state == "initial" assert self.state == "initial"
self.state = "entered" self.state = "entered"
try: try:
return self.gen.next() return self.gen.next()
except StopIteration: except StopIteration:
self.state = "error" self.state = "error"
raise RuntimeError("template generator didn't yield") raise RuntimeError("template generator didn't yield")
def __exit__(self, *args): def __exit__(self, *args):
assert self.state == "entered" assert self.state == "entered"
self.state = "exited" self.state = "exited"
try: try:
self.gen.next() self.gen.next()
except StopIteration: except StopIteration:
return return
else: else:
self.state = "error" self.state = "error"
raise RuntimeError("template generator didn't stop") raise RuntimeError("template generator didn't stop")
def do_template(func): def do_template(func):
def helper(*args, **kwds): def helper(*args, **kwds):
return Wrapper(func(*args, **kwds)) return Wrapper(func(*args, **kwds))
return helper return helper
This decorator could be used as follows: This decorator could be used as follows:
@do_template @do_template
def opening(filename): def opening(filename):
f = open(filename) # IOError here is untouched by Wrapper f = open(filename) # IOError here is untouched by Wrapper
yield f yield f
f.close() # Ditto for errors here (however unlikely) f.close() # Ditto for errors here (however unlikely)
A robust implementation of such a decorator should be made part of A robust implementation of such a decorator should be made part of
the standard library. the standard library.
@ -151,14 +156,14 @@ Examples
class transactional: class transactional:
def __init__(self, db): def __init__(self, db):
self.db = db self.db = db
def __enter__(self): def __enter__(self):
pass pass
def __exit__(self, *args): def __exit__(self, *args):
if args and args[0] is not None: if args and args[0] is not None:
self.db.rollback() self.db.rollback()
else: else:
self.db.commit() self.db.commit()
4. Example 1 rewritten without a generator: 4. Example 1 rewritten without a generator:
@ -166,9 +171,9 @@ Examples
def __init__(self, lock): def __init__(self, lock):
self.lock = lock self.lock = lock
def __enter__(self): def __enter__(self):
self.lock.acquire() self.lock.acquire()
def __exit__(self, *args): def __exit__(self, *args):
self.lock.release() self.lock.release()
(This example is easily modified to implement the other (This example is easily modified to implement the other
examples; it shows how much simpler generators are for the same examples; it shows how much simpler generators are for the same
@ -179,11 +184,9 @@ Examples
@do_template @do_template
def redirecting_stdout(new_stdout): def redirecting_stdout(new_stdout):
save_stdout = sys.stdout save_stdout = sys.stdout
try: sys.stdout = new_stdout
sys.stdout = new_stdout yield None
yield None sys.stdout = save_stdout
finally:
sys.stdout = save_stdout
Used as follows: Used as follows:
@ -200,7 +203,7 @@ Examples
except IOError, err: except IOError, err:
yield None, err yield None, err
else: else:
yield f, None yield f, None
f.close() f.close()
Used as follows: Used as follows: