Add PEP 343: Anonymous Block Redux
This commit is contained in:
parent
4f5abc4b4f
commit
3e348d5237
|
@ -120,6 +120,7 @@ Index by Category
|
||||||
S 340 Anonymous Block Statements GvR
|
S 340 Anonymous Block Statements GvR
|
||||||
S 341 Unifying try-except and try-finally Birkenfeld
|
S 341 Unifying try-except and try-finally Birkenfeld
|
||||||
S 342 Enhanced Iterators GvR
|
S 342 Enhanced Iterators GvR
|
||||||
|
S 343 Anonymous Block Redux GvR
|
||||||
S 754 IEEE 754 Floating Point Special Values Warnes
|
S 754 IEEE 754 Floating Point Special Values Warnes
|
||||||
|
|
||||||
Finished PEPs (done, implemented in CVS)
|
Finished PEPs (done, implemented in CVS)
|
||||||
|
@ -380,6 +381,7 @@ Numerical Index
|
||||||
S 340 Anonymous Block Statements GvR
|
S 340 Anonymous Block Statements GvR
|
||||||
S 341 Unifying try-except and try-finally Birkenfeld
|
S 341 Unifying try-except and try-finally Birkenfeld
|
||||||
S 342 Enhanced Iterators GvR
|
S 342 Enhanced Iterators GvR
|
||||||
|
S 343 Anonymous Block Redux GvR
|
||||||
SR 666 Reject Foolish Indentation Creighton
|
SR 666 Reject Foolish Indentation Creighton
|
||||||
S 754 IEEE 754 Floating Point Special Values Warnes
|
S 754 IEEE 754 Floating Point Special Values Warnes
|
||||||
I 3000 Python 3.0 Plans Kuchling, Cannon
|
I 3000 Python 3.0 Plans Kuchling, Cannon
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue