[Python-Dev] PEP 343 - Abstract Block Redux
Nick Coghlan
ncoghlan at gmail.com
Sat May 14 11:37:36 CEST 2005
Guido van Rossum wrote:
> I've written up the specs for my "PEP 340 redux" proposal as a
> separate PEP, PEP 343.
>
> http://python.org/peps/pep-0343.html
>
> Those who have been following the thread "Merging PEP 310 and PEP
> 340-redux?" will recognize my proposal in that thread, which received
> mostly positive responses there.
>
> Please review and ask for clarifications of anything that's unclear.
On the keyword front, the two keyword choices affect the naming conventions of
templates differently, and I think need to be considered in that light.
The naming convention for 'do' is shown in the current PEP 343. The issue I've
noticed with it is that *functions* read well, but methods don't because things
get out of sequence. That is, "do locking(the_lock)" reads well, but "do
the_lock.locking()" does not.
Whereas, using 'with', it can be written either way, and still read reasonably
well ("with locked(the_lock)", "with the_lock.locked()").
The 'with' keyword also reads better if objects natively support use in 'with'
blocks ("with the_lock", "with the_file").
Guido's concern regarding file objects being reused inappropriately can be dealt
with in the file __enter__ method:
def __enter__(self):
if self.closed:
raise RuntimeError, "Cannot reopen closed file handle"
Template generators have the exact same problem with reusability - the solution
used there is raising a RuntimeError when __enter__() is called inappropriately.
This would make sense as a standard idiom - if a statement template can't be
reused, attempting to do so should trigger a RuntimeError the second time
__enter__() is invoked.
For files, it may then become the common practice to keep pathnames around,
rather than open file handles. When you actually needed access to the file, the
existing "open" builtin would suffice:
with open(filename, "rb") as f:
for line in f:
print line
I've written out the PEP 343 examples below, assuming types acquire native with
statement support (including Decimal contexts - I also give PEP 343 style code
for Decimal contexts).
PEP343 examples: 'with' keyword, native support in objects
1. A template for ensuring that a lock, acquired at the start of a
block, is released when the block is left:
# New methods on lock objects
def __enter__(self):
self.acquire()
def __exit__(self, *args):
self.release()
Used as follows:
with 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:
# New methods on file objects
def __enter__(self):
if self.closed:
raise RuntimeError, "Cannot reopen closed file handle"
def __exit__(self, *args):
self.close()
Used as follows:
with open("/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 transaction:
def __init__(self, db):
self.db = db
def __enter__(self):
self.db.begin()
def __exit__(self, *args):
if args and args[0] is not None:
self.db.rollback()
else:
self.db.commit()
Used as follows:
with transaction(db):
# Exceptions in this code cause a rollback
5. Redirect stdout temporarily:
@with_template
def redirected_stdout(new_stdout):
save_stdout = sys.stdout
sys.stdout = new_stdout
yield None
sys.stdout = save_stdout
Used as follows:
with open(filename, "w") as f:
with redirected_stdout(f):
print "Hello world"
This isn't thread-safe, of course, but neither is doing this
same dance manually. In a single-threaded program (e.g., a
script) it is a totally fine way of doing things.
6. A variant on opening() that also returns an error condition:
@with_template
def open_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:
with open_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:
from signal import blocked_signals
with blocked_signals():
# 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.
8. Another use for this feature is the Decimal context.
# New methods on decimal Context objects
def __enter__(self):
self._old_context = getcontext()
setcontext(self)
def __exit__(self, *args):
setcontext(self._old_context)
Used as follows:
with decimal.Context(precision=28):
# Code here executes with the given context
# The context always reverts after this statement
For comparison, the equivalent PEP 343 code is:
@do_template
def with_context(context):
old_context = getcontext()
setcontext(context)
yield None
setcontext(old_context)
Used as:
do decimal.with_context(decimal.Context(precision=28)):
# Code here executes with the given context
# The context always reverts after this statement
--
Nick Coghlan | ncoghlan at gmail.com | Brisbane, Australia
---------------------------------------------------------------
http://boredomandlaziness.blogspot.com
More information about the Python-Dev
mailing list