python-peps/pep-0343.txt

232 lines
6.8 KiB
Plaintext
Raw Normal View History

2005-05-13 20:08:20 -04:00
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
exc = () # Or (None, None, None) ?
2005-05-13 20:08:20 -04:00
try:
try:
VAR = abc.__enter__()
BLOCK
except:
exc = sys.exc_info()
raise
2005-05-13 20:08:20 -04:00
finally:
abc.__exit__(exc)
2005-05-13 20:08:20 -04:00
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 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.
2005-05-13 20:08:20 -04:00
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
2005-05-13 20:08:20 -04:00
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)
2005-05-13 20:08:20 -04:00
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
2005-05-13 20:08:20 -04:00
def __enter__(self):
pass
def __exit__(self, *args):
if args and args[0] is not None:
self.db.rollback()
else:
self.db.commit()
2005-05-13 20:08:20 -04:00
4. Example 1 rewritten without a generator:
class locking:
def __init__(self, lock):
self.lock = lock
def __enter__(self):
self.lock.acquire()
2005-05-13 20:08:20 -04:00
def __exit__(self, *args):
self.lock.release()
2005-05-13 20:08:20 -04:00
(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
sys.stdout = new_stdout
yield None
sys.stdout = save_stdout
2005-05-13 20:08:20 -04:00
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
2005-05-13 20:08:20 -04:00
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.