From e9086b2485e74bb1792cd328d19ad6ee2cf8597f Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 14 May 2005 02:02:40 +0000 Subject: [PATCH] Don't be wishy-washy about the call to __exit__(). Fix the redirecting_stdout() example (remove the try/finally). --- pep-0343.txt | 115 ++++++++++++++++++++++++++------------------------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/pep-0343.txt b/pep-0343.txt index f092739b1..458e85b8f 100644 --- a/pep-0343.txt +++ b/pep-0343.txt @@ -46,23 +46,28 @@ Specification The translation of the above statement is: abc = EXPR + exc = () # Or (None, None, None) ? try: - VAR = abc.__enter__() - BLOCK + try: + VAR = abc.__enter__() + BLOCK + except: + exc = sys.exc_info() + raise 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 the translation is omitted (but abc.__enter__() is still called). - The call to abc.__exit__() is only approximated as written. The - actual calling convention is: If the finally-suite was reached - through normal completion of BLOCK or through a "non-local goto" - (a break, continue or return statement in BLOCK), abc.__exit__() - is called without arguments (or perhaps with three None - arguments). If the finally-suite was reached through an exception - raised in BLOCK, abc.__exit__() is called with three arguments - representing the exception type, value, and traceback. + The calling convention for abc.__exit__() is: as follows. If the + finally-suite was reached through normal completion of BLOCK or + through a "non-local goto" (a break, continue or return statement + in BLOCK), abc.__exit__() is called without arguments (or perhaps + with three None arguments?). If the finally-suite was reached + through an exception raised in BLOCK, abc.__exit__() is called + with three arguments representing the exception type, value, and + traceback. Optional Generator Decorator @@ -70,41 +75,41 @@ Optional Generator Decorator a generator that yields exactly once to control a do-statement. Here's a sketch of such a decorator: - class Wrapper(object): - def __init__(self, gen): - self.gen = gen - self.state = "initial" - def __enter__(self): - assert self.state == "initial" - self.state = "entered" - try: - return self.gen.next() - except StopIteration: - self.state = "error" - raise RuntimeError("template generator didn't yield") - def __exit__(self, *args): - assert self.state == "entered" - self.state = "exited" - try: - self.gen.next() - except StopIteration: - return - else: - self.state = "error" - raise RuntimeError("template generator didn't stop") + class Wrapper(object): + def __init__(self, gen): + self.gen = gen + self.state = "initial" + def __enter__(self): + assert self.state == "initial" + self.state = "entered" + try: + return self.gen.next() + except StopIteration: + self.state = "error" + raise RuntimeError("template generator didn't yield") + def __exit__(self, *args): + assert self.state == "entered" + self.state = "exited" + try: + self.gen.next() + except StopIteration: + return + else: + self.state = "error" + raise RuntimeError("template generator didn't stop") - def do_template(func): - def helper(*args, **kwds): - return Wrapper(func(*args, **kwds)) - return helper + def do_template(func): + def helper(*args, **kwds): + return Wrapper(func(*args, **kwds)) + return helper This decorator could be used as follows: - @do_template - def opening(filename): - f = open(filename) # IOError here is untouched by Wrapper - yield f - f.close() # Ditto for errors here (however unlikely) + @do_template + def opening(filename): + f = open(filename) # IOError here is untouched by Wrapper + yield f + f.close() # Ditto for errors here (however unlikely) A robust implementation of such a decorator should be made part of the standard library. @@ -151,14 +156,14 @@ Examples class transactional: def __init__(self, db): - self.db = db + self.db = db def __enter__(self): pass - def __exit__(self, *args): - if args and args[0] is not None: - self.db.rollback() - else: - self.db.commit() + def __exit__(self, *args): + if args and args[0] is not None: + self.db.rollback() + else: + self.db.commit() 4. Example 1 rewritten without a generator: @@ -166,9 +171,9 @@ Examples def __init__(self, lock): self.lock = lock def __enter__(self): - self.lock.acquire() + self.lock.acquire() def __exit__(self, *args): - self.lock.release() + self.lock.release() (This example is easily modified to implement the other examples; it shows how much simpler generators are for the same @@ -179,11 +184,9 @@ Examples @do_template def redirecting_stdout(new_stdout): save_stdout = sys.stdout - try: - sys.stdout = new_stdout - yield None - finally: - sys.stdout = save_stdout + sys.stdout = new_stdout + yield None + sys.stdout = save_stdout Used as follows: @@ -200,7 +203,7 @@ Examples except IOError, err: yield None, err else: - yield f, None + yield f, None f.close() Used as follows: