From 3e348d5237ae2231184484f88be61cd88610e954 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sat, 14 May 2005 00:08:20 +0000 Subject: [PATCH] Add PEP 343: Anonymous Block Redux --- pep-0000.txt | 2 + pep-0343.txt | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 pep-0343.txt diff --git a/pep-0000.txt b/pep-0000.txt index 37d1ea5ad..e8de35a69 100644 --- a/pep-0000.txt +++ b/pep-0000.txt @@ -120,6 +120,7 @@ Index by Category S 340 Anonymous Block Statements GvR S 341 Unifying try-except and try-finally Birkenfeld S 342 Enhanced Iterators GvR + S 343 Anonymous Block Redux GvR S 754 IEEE 754 Floating Point Special Values Warnes Finished PEPs (done, implemented in CVS) @@ -380,6 +381,7 @@ Numerical Index S 340 Anonymous Block Statements GvR S 341 Unifying try-except and try-finally Birkenfeld S 342 Enhanced Iterators GvR + S 343 Anonymous Block Redux GvR SR 666 Reject Foolish Indentation Creighton S 754 IEEE 754 Floating Point Special Values Warnes I 3000 Python 3.0 Plans Kuchling, Cannon diff --git a/pep-0343.txt b/pep-0343.txt new file mode 100644 index 000000000..f092739b1 --- /dev/null +++ b/pep-0343.txt @@ -0,0 +1,228 @@ +PEP: 343 +Title: Anonymous Block Redux +Version: $Revision$ +Last-Modified: $Date$ +Author: Guido van Rossum +Status: Draft +Type: Standards Track +Content-Type: text/plain +Created: 13-May-2005 +Post-History: + +Introduction + + After a lot of discussion about PEP 340 and alternatives, I've + decided to withdraw PEP 340 and propose a slight variant on PEP + 310. + +Motivation and Summary + + TBD. + +Use Cases + + See the Examples section near the end. + +Specification + + A new statement is proposed with the syntax + + do EXPR as VAR: + BLOCK + + Here, 'do' and 'as' are new keywords; EXPR is an arbitrary + expression (but not an expression-list) and VAR is an arbitrary + assignment target (which may be a comma-separated list). + + The "as VAR" part is optional. + + The choice of the 'do' keyword is provisional; an alternative + under consideration is 'with'. + + A yield-statement is illegal inside BLOCK. This is because the + do-statement is translated into a try/finally statement, and yield + is illegal in a try/finally statement. + + The translation of the above statement is: + + abc = EXPR + try: + VAR = abc.__enter__() + BLOCK + finally: + abc.__exit__(*sys.exc_info()) # XXX See below + + 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. + +Optional Generator Decorator + + It is possible to write a decorator that makes it possible to use + 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") + + 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) + + A robust implementation of such a decorator should be made part of + the standard library. + +Examples + + Several of these examples contain "yield None". If PEP 342 is + accepted, these can be changed to just "yield". + + 1. A template for ensuring that a lock, acquired at the start of a + block, is released when the block is left: + + @do_template + def locking(lock): + lock.acquire() + yield None + lock.release() + + Used as follows: + + do locking(myLock): + # Code here executes with myLock held. The lock is + # guaranteed to be released when the block is left (even + # if via return or by an uncaught exception). + + 2. A template for opening a file that ensures the file is closed + when the block is left: + + @do_template + def opening(filename, mode="r"): + f = open(filename, mode) + yield f + f.close() + + Used as follows: + + do opening("/etc/passwd") as f: + for line in f: + print line.rstrip() + + 3. A template for committing or rolling back a database + transaction; this is written as a class rather than as a + decorator since it requires access to the exception information: + + class transactional: + def __init__(self, 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() + + 4. Example 1 rewritten without a generator: + + class locking: + def __init__(self, lock): + self.lock = lock + def __enter__(self): + self.lock.acquire() + def __exit__(self, *args): + self.lock.release() + + (This example is easily modified to implement the other + examples; it shows how much simpler generators are for the same + purpose.) + + 5. Redirect stdout temporarily: + + @do_template + def redirecting_stdout(new_stdout): + save_stdout = sys.stdout + try: + sys.stdout = new_stdout + yield None + finally: + sys.stdout = save_stdout + + Used as follows: + + do opening(filename, "w") as f: + do redirecting_stdout(f): + print "Hello world" + + 6. A variant on opening() that also returns an error condition: + + @do_template + def opening_w_error(filename, mode="r"): + try: + f = open(filename, mode) + except IOError, err: + yield None, err + else: + yield f, None + f.close() + + Used as follows: + + do opening_w_error("/etc/passwd", "a") as f, err: + if err: + print "IOError:", err + else: + f.write("guido::0:0::/:/bin/sh\n") + + 7. Another useful example would be an operation that blocks + signals. The use could be like this: + + import signal + + do signal.blocking(): + # code executed without worrying about signals + + An optional argument might be a list of signals to be blocked; + by default all signals are blocked. The implementation is left + as an exercise to the reader. + +Copyright + + This document has been placed in the public domain.